From df3074fdfed0e2dbac5bf26138395b79fd14de2b Mon Sep 17 00:00:00 2001 From: Jason Teplitz Date: Sun, 20 Dec 2015 18:10:36 -0500 Subject: [PATCH] feat(core/application_ref): Allow asyncronous app initializers. closes #5929. Closes #6063 --- modules/angular2/src/core/application_ref.ts | 54 +++++++-- .../test/core/application_ref_spec.ts | 111 ++++++++++++++++-- 2 files changed, 143 insertions(+), 22 deletions(-) diff --git a/modules/angular2/src/core/application_ref.ts b/modules/angular2/src/core/application_ref.ts index d4d799bea0..3dd2e35da3 100644 --- a/modules/angular2/src/core/application_ref.ts +++ b/modules/angular2/src/core/application_ref.ts @@ -217,25 +217,36 @@ export class PlatformRef_ extends PlatformRef { application(providers: Array): 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 app; } asyncApplication(bindingFn: (zone: NgZone) => Promise>, additionalProviders?: Array): Promise { var zone = createNgZone(); var completer = PromiseWrapper.completer(); - zone.run(() => { - PromiseWrapper.then(bindingFn(zone), (providers: Array) => { - if (isPresent(additionalProviders)) { - providers = ListWrapper.concat(providers, additionalProviders); - } - completer.resolve(this._initApp(zone, providers)); + if (bindingFn === null) { + completer.resolve(this._initApp(zone, additionalProviders)); + } else { + zone.run(() => { + PromiseWrapper.then(bindingFn(zone), (providers: Array) => { + if (isPresent(additionalProviders)) { + providers = ListWrapper.concat(providers, additionalProviders); + } + let promise = this._initApp(zone, providers); + completer.resolve(promise); + }); }); - }); + } return completer.promise; } - private _initApp(zone: NgZone, providers: Array): ApplicationRef { + private _initApp(zone: NgZone, + providers: Array): Promise| + ApplicationRef { var injector: Injector; var app: ApplicationRef; zone.run(() => { @@ -259,8 +270,12 @@ export class PlatformRef_ extends PlatformRef { }); app = new ApplicationRef_(this, zone, injector); this._applications.push(app); - _runAppInitializers(injector); - return app; + var promise = _runAppInitializers(injector); + if (promise !== null) { + return PromiseWrapper.then(promise, (_) => app); + } else { + return app; + } } dispose(): void { @@ -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 { let inits: Function[] = injector.getOptional(APP_INITIALIZER); - if (isPresent(inits)) inits.forEach(init => init()); + let promises: Promise[] = []; + 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; + } } /** diff --git a/modules/angular2/test/core/application_ref_spec.ts b/modules/angular2/test/core/application_ref_spec.ts index 0f54b9c80f..0d24f350c9 100644 --- a/modules/angular2/test/core/application_ref_spec.ts +++ b/modules/angular2/test/core/application_ref_spec.ts @@ -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): 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 = 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 = 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."); + })); }); }); }