feat(core/application_ref): Allow asyncronous app initializers.

closes #5929.

Closes #6063
This commit is contained in:
Jason Teplitz 2015-12-20 18:10:36 -05:00
parent f7424d5aeb
commit df3074fdfe
2 changed files with 143 additions and 22 deletions

View File

@ -217,25 +217,36 @@ export class PlatformRef_ extends PlatformRef {
application(providers: Array<Type | Provider | any[]>): ApplicationRef {
var app = this._initApp(createNgZone(), providers);
return app;
if (PromiseWrapper.isPromise(app)) {
throw new BaseException(
"Cannot use asyncronous app initializers with application. Use asyncApplication instead.");
}
return <ApplicationRef>app;
}
asyncApplication(bindingFn: (zone: NgZone) => Promise<Array<Type | Provider | any[]>>,
additionalProviders?: Array<Type | Provider | any[]>): Promise<ApplicationRef> {
var zone = createNgZone();
var completer = PromiseWrapper.completer();
if (bindingFn === null) {
completer.resolve(this._initApp(zone, additionalProviders));
} else {
zone.run(() => {
PromiseWrapper.then(bindingFn(zone), (providers: Array<Type | Provider | any[]>) => {
if (isPresent(additionalProviders)) {
providers = ListWrapper.concat(providers, additionalProviders);
}
completer.resolve(this._initApp(zone, providers));
let promise = this._initApp(zone, providers);
completer.resolve(promise);
});
});
}
return completer.promise;
}
private _initApp(zone: NgZone, providers: Array<Type | Provider | any[]>): ApplicationRef {
private _initApp(zone: NgZone,
providers: Array<Type | Provider | any[]>): Promise<ApplicationRef>|
ApplicationRef {
var injector: Injector;
var app: ApplicationRef;
zone.run(() => {
@ -259,9 +270,13 @@ export class PlatformRef_ extends PlatformRef {
});
app = new ApplicationRef_(this, zone, injector);
this._applications.push(app);
_runAppInitializers(injector);
var promise = _runAppInitializers(injector);
if (promise !== null) {
return PromiseWrapper.then(promise, (_) => app);
} else {
return app;
}
}
dispose(): void {
ListWrapper.clone(this._applications).forEach((app) => app.dispose());
@ -273,9 +288,22 @@ export class PlatformRef_ extends PlatformRef {
_applicationDisposed(app: ApplicationRef): void { ListWrapper.remove(this._applications, app); }
}
function _runAppInitializers(injector: Injector): void {
function _runAppInitializers(injector: Injector): Promise<any> {
let inits: Function[] = injector.getOptional(APP_INITIALIZER);
if (isPresent(inits)) inits.forEach(init => init());
let promises: Promise<any>[] = [];
if (isPresent(inits)) {
inits.forEach(init => {
var retVal = init();
if (PromiseWrapper.isPromise(retVal)) {
promises.push(retVal);
}
});
}
if (promises.length > 0) {
return PromiseWrapper.all(promises);
} else {
return null;
}
}
/**

View File

@ -11,13 +11,14 @@ import {
AsyncTestCompleter,
fakeAsync,
tick,
inject
inject,
SpyObject
} from 'angular2/testing_internal';
import {SpyChangeDetector} from './spies';
import {ApplicationRef_, PlatformRef_} from "angular2/src/core/application_ref";
import {Injector, Provider} from "angular2/core";
import {ApplicationRef_, ApplicationRef, PlatformRef_} from "angular2/src/core/application_ref";
import {Injector, Provider, APP_INITIALIZER} from "angular2/core";
import {ChangeDetectorRef_} from "angular2/src/core/change_detection/change_detector_ref";
import {PromiseWrapper} from "angular2/src/facade/async";
import {PromiseWrapper, PromiseCompleter, TimerWrapper} from "angular2/src/facade/async";
import {ListWrapper} from "angular2/src/facade/collection";
export function main() {
@ -33,7 +34,14 @@ export function main() {
describe("PlatformRef", () => {
describe("asyncApplication", () => {
it("should merge synchronous and asynchronous providers",
function expectProviders(injector: Injector, providers: Array<any>): void {
for (let i = 0; i < providers.length; i++) {
let provider = providers[i];
expect(injector.get(provider.token)).toBe(provider.useValue);
}
}
it("should merge syncronous and asyncronous providers",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let ASYNC_PROVIDERS = [new Provider(Foo, {useValue: new Foo()})];
@ -41,13 +49,98 @@ export function main() {
ref.asyncApplication((zone) => PromiseWrapper.resolve(ASYNC_PROVIDERS), SYNC_PROVIDERS)
.then((appRef) => {
var providers = ListWrapper.concat(ASYNC_PROVIDERS, SYNC_PROVIDERS);
for (var i = 0; i < providers.length; i++) {
var provider = providers[i];
expect(appRef.injector.get(provider.token)).toBe(provider.useValue);
}
expectProviders(appRef.injector, providers);
async.done();
});
}));
it("should allow function to be null",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let SYNC_PROVIDERS = [new Provider(Bar, {useValue: new Bar()})];
ref.asyncApplication(null, SYNC_PROVIDERS)
.then((appRef) => {
expectProviders(appRef.injector, SYNC_PROVIDERS);
async.done();
});
}));
function mockAsyncAppInitializer(completer, providers: Array<any> = null,
injector?: Injector) {
return () => {
if (providers != null) {
expectProviders(injector, providers);
}
TimerWrapper.setTimeout(() => completer.resolve(true), 1);
return completer.promise;
};
}
function createSpyPromiseCompleter(): SpyObject {
let completer = PromiseWrapper.completer();
let completerSpy = <any>new SpyObject();
// Note that in TypeScript we need to provide a value for the promise attribute
// whereas in dart we need to override the promise getter
completerSpy.promise = completer.promise;
completerSpy.spy("get:promise").andReturn(completer.promise);
completerSpy.spy("resolve").andCallFake(completer.resolve);
completerSpy.spy("reject").andCallFake(completer.reject);
return completerSpy;
}
it("should wait for asyncronous app initializers",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let completer = createSpyPromiseCompleter();
let SYNC_PROVIDERS = [
new Provider(Bar, {useValue: new Bar()}),
new Provider(APP_INITIALIZER,
{useValue: mockAsyncAppInitializer(completer), multi: true})
];
ref.asyncApplication(null, SYNC_PROVIDERS)
.then((appRef) => {
expectProviders(appRef.injector,
SYNC_PROVIDERS.slice(0, SYNC_PROVIDERS.length - 1));
expect(completer.spy("resolve")).toHaveBeenCalled();
async.done();
});
}));
it("should wait for async providers and then async app initializers",
inject([AsyncTestCompleter, Injector], (async, injector) => {
let ref = new PlatformRef_(injector, null);
let ASYNC_PROVIDERS = [new Provider(Foo, {useValue: new Foo()})];
let completer = createSpyPromiseCompleter();
let SYNC_PROVIDERS = [
new Provider(Bar, {useValue: new Bar()}),
new Provider(APP_INITIALIZER,
{
useFactory: (injector) => mockAsyncAppInitializer(
completer, ASYNC_PROVIDERS, injector),
multi: true,
deps: [Injector]
})
];
ref.asyncApplication((zone) => PromiseWrapper.resolve(ASYNC_PROVIDERS), SYNC_PROVIDERS)
.then((appRef) => {
expectProviders(appRef.injector,
SYNC_PROVIDERS.slice(0, SYNC_PROVIDERS.length - 1));
expect(completer.spy("resolve")).toHaveBeenCalled();
async.done();
});
}));
});
describe("application", () => {
it("should throw if an APP_INITIIALIZER returns a promise", inject([Injector], (injector) => {
let ref = new PlatformRef_(injector, null);
let appInitializer = new Provider(
APP_INITIALIZER, {useValue: () => PromiseWrapper.resolve([]), multi: true});
expect(() => ref.application([appInitializer]))
.toThrowError(
"Cannot use asyncronous app initializers with application. Use asyncApplication instead.");
}));
});
});
}