refactor(core): Introduce `AppInitStatus`

This class allows any provider to know and wait for the initialization of the
application. This functionality previously was tied to `ApplicationRef`.

BREAKING CHANGE:
- `ApplicationRef.waitForAsyncInitializers` is deprecated. Use 
  `AppInitStatus.donePromise` / `AppInitStatus.done` instead.
This commit is contained in:
Tobias Bosch 2016-08-02 07:38:14 -07:00
parent 7e4fd7d7da
commit 630028350a
7 changed files with 147 additions and 31 deletions

View File

@ -15,7 +15,8 @@ export * from './src/metadata';
export * from './src/util';
export * from './src/di';
export {createPlatform, assertPlatform, disposePlatform, getPlatform, coreBootstrap, coreLoadAndBootstrap, PlatformRef, ApplicationRef, enableProdMode, lockRunMode, isDevMode, createPlatformFactory} from './src/application_ref';
export {APP_ID, APP_INITIALIZER, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, APP_BOOTSTRAP_LISTENER} from './src/application_tokens';
export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, APP_BOOTSTRAP_LISTENER} from './src/application_tokens';
export {APP_INITIALIZER, AppInitStatus} from './src/application_init';
export * from './src/zone';
export * from './src/render';
export * from './src/linker';

View File

@ -0,0 +1,49 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {isPromise} from '../src/facade/lang';
import {Inject, Injectable, OpaqueToken, Optional} from './di';
/**
* A function that will be executed when an application is initialized.
* @experimental
*/
export const APP_INITIALIZER: any = new OpaqueToken('Application Initializer');
/**
* A class that reflects the state of running {@link APP_INITIALIZER}s.
*
* @experimental
*/
@Injectable()
export class AppInitStatus {
private _donePromise: Promise<any>;
private _done = false;
constructor(@Inject(APP_INITIALIZER) @Optional() appInits: (() => any)[]) {
const asyncInitPromises: Promise<any>[] = [];
if (appInits) {
for (let i = 0; i < appInits.length; i++) {
const initResult = appInits[i]();
if (isPromise(initResult)) {
asyncInitPromises.push(initResult);
}
}
}
this._donePromise = Promise.all(asyncInitPromises).then(() => { this._done = true; });
if (asyncInitPromises.length === 0) {
this._done = true;
}
}
get done(): boolean { return this._done; }
get donePromise(): Promise<any> { return this._donePromise; }
}

View File

@ -8,6 +8,7 @@
import {Type} from '../src/facade/lang';
import {AppInitStatus} from './application_init';
import {ApplicationRef, ApplicationRef_, isDevMode} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
@ -44,6 +45,7 @@ export const APPLICATION_COMMON_PROVIDERS: Array<Type|{[k: string]: any}|any[]>
providers: [
ApplicationRef_,
{provide: ApplicationRef, useExisting: ApplicationRef_},
AppInitStatus,
Compiler,
{provide: ComponentResolver, useExisting: Compiler},
APP_ID_RANDOM_PROVIDER,

View File

@ -11,7 +11,8 @@ import {ListWrapper} from '../src/facade/collection';
import {BaseException, ExceptionHandler, unimplemented} from '../src/facade/exceptions';
import {ConcreteType, Type, isBlank, isPresent, isPromise, stringify} from '../src/facade/lang';
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, PLATFORM_INITIALIZER} from './application_tokens';
import {AppInitStatus} from './application_init';
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
import {ChangeDetectorRef} from './change_detection/change_detector_ref';
import {Console} from './console';
import {Inject, Injectable, Injector, OpaqueToken, Optional, ReflectiveInjector, SkipSelf, forwardRef} from './di';
@ -359,19 +360,8 @@ export class PlatformRef_ extends PlatformRef {
exceptionHandler.call(error.error, error.stackTrace);
});
return _callAndReportToExceptionHandler(exceptionHandler, () => {
const appInits = moduleRef.injector.get(APP_INITIALIZER, null);
const asyncInitPromises: Promise<any>[] = [];
if (isPresent(appInits)) {
for (let i = 0; i < appInits.length; i++) {
const initResult = appInits[i]();
if (isPromise(initResult)) {
asyncInitPromises.push(initResult);
}
}
}
const appRef: ApplicationRef_ = moduleRef.injector.get(ApplicationRef);
return Promise.all(asyncInitPromises).then(() => {
appRef.asyncInitDone();
const initStatus: AppInitStatus = moduleRef.injector.get(AppInitStatus);
return initStatus.donePromise.then(() => {
this._moduleDoBootstrap(moduleRef);
return moduleRef;
});
@ -430,6 +420,8 @@ export abstract class ApplicationRef {
/**
* Returns a promise that resolves when all asynchronous application initializers
* are done.
*
* @deprecated Use the {@link AppInitStatus} class instead.
*/
abstract waitForAsyncInitializers(): Promise<any>;
@ -509,13 +501,11 @@ export class ApplicationRef_ extends ApplicationRef {
private _runningTick: boolean = false;
private _enforceNoNewChanges: boolean = false;
/** @internal */
_asyncInitDonePromise: PromiseCompleter<any> = PromiseWrapper.completer();
constructor(
private _zone: NgZone, private _console: Console, private _injector: Injector,
private _exceptionHandler: ExceptionHandler,
private _componentFactoryResolver: ComponentFactoryResolver,
private _initStatus: AppInitStatus,
@Optional() private _testabilityRegistry: TestabilityRegistry,
@Optional() private _testability: Testability) {
super();
@ -545,11 +535,9 @@ export class ApplicationRef_ extends ApplicationRef {
}
/**
* @internal
* @deprecated
*/
asyncInitDone() { this._asyncInitDonePromise.resolve(null); }
waitForAsyncInitializers(): Promise<any> { return this._asyncInitDonePromise.promise; }
waitForAsyncInitializers(): Promise<any> { return this._initStatus.donePromise; }
run(callback: Function): any {
return this._zone.run(
@ -557,6 +545,10 @@ export class ApplicationRef_ extends ApplicationRef {
}
bootstrap<C>(componentOrFactory: ComponentFactory<C>|ConcreteType<C>): ComponentRef<C> {
if (!this._initStatus.done) {
throw new BaseException(
'Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.');
}
return this.run(() => {
let componentFactory: ComponentFactory<C>;
if (componentOrFactory instanceof ComponentFactory) {

View File

@ -47,12 +47,6 @@ function _randomChar(): string {
*/
export const PLATFORM_INITIALIZER: any = new OpaqueToken('Platform Initializer');
/**
* A function that will be executed when an application is initialized.
* @experimental
*/
export const APP_INITIALIZER: any = new OpaqueToken('Application Initializer');
/**
* All callbacks provided via this token will be called when a component has been bootstrapped.
*

View File

@ -0,0 +1,50 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {APP_INITIALIZER, AppInitStatus} from '../src/application_init';
import {PromiseCompleter, PromiseWrapper} from '../src/facade/async';
import {TestBed, async, inject, withModule} from '../testing';
export function main() {
describe('AppInitStatus', () => {
describe('no initializers', () => {
it('should return true for `done`',
inject([AppInitStatus], (status: AppInitStatus) => { expect(status.done).toBe(true); }));
it('should return a promise that resolves immediately for `donePromise`',
async(inject([AppInitStatus], (status: AppInitStatus) => {
status.donePromise.then(() => { expect(status.done).toBe(true); });
})));
});
describe('with async initializers', () => {
let completer: PromiseCompleter<any>;
beforeEach(() => {
completer = PromiseWrapper.completer();
TestBed.configureTestingModule({
providers: [{provide: APP_INITIALIZER, multi: true, useValue: () => completer.promise}]
});
});
it('should updat the status once all async initializers are done',
async(inject([AppInitStatus], (status: AppInitStatus) => {
let completerResolver = false;
setTimeout(() => {
completerResolver = true;
completer.resolve();
});
expect(status.done).toBe(false);
status.donePromise.then(() => {
expect(status.done).toBe(true);
expect(completerResolver).toBe(true);
});
})));
});
});
}

View File

@ -19,7 +19,7 @@ import {PromiseCompleter, PromiseWrapper} from '../src/facade/async';
import {ExceptionHandler} from '../src/facade/exception_handler';
import {BaseException} from '../src/facade/exceptions';
import {ConcreteType} from '../src/facade/lang';
import {TestBed, async, inject} from '../testing';
import {TestBed, async, inject, withModule} from '../testing';
import {SpyChangeDetectorRef} from './spies';
@ -133,7 +133,27 @@ export function main() {
const compRef = ref.bootstrap(SomeComponent);
expect(capturedCompRefs).toEqual([compRef]);
}));
});
describe('bootstrap', () => {
beforeEach(
() => {
});
it('should throw if an APP_INITIIALIZER is not yet resolved',
withModule(
{
providers: [{
provide: APP_INITIALIZER,
useValue: () => PromiseWrapper.completer().promise,
multi: true
}]
},
inject([ApplicationRef], (ref: ApplicationRef_) => {
expect(() => ref.bootstrap(SomeComponent))
.toThrowError(
'Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.');
})));
});
});
@ -163,7 +183,11 @@ export function main() {
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]))
.then(() => expect(false).toBe(true), (e) => {
expect(e).toBe('Test');
expect(errorLogger.res).toEqual(['EXCEPTION: Test']);
// Note: if the modules throws an error during construction,
// we don't have an injector and therefore no way of
// getting the exception handler. So
// the error is only rethrown but not logged via the exception handler.
expect(errorLogger.res).toEqual([]);
});
}));
@ -248,7 +272,11 @@ export function main() {
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]));
expect(() => defaultPlatform.bootstrapModuleFactory(moduleFactory)).toThrow('Test');
expect(errorLogger.res).toEqual(['EXCEPTION: Test']);
// Note: if the modules throws an error during construction,
// we don't have an injector and therefore no way of
// getting the exception handler. So
// the error is only rethrown but not logged via the exception handler.
expect(errorLogger.res).toEqual([]);
}));
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',