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:
parent
7e4fd7d7da
commit
630028350a
|
@ -15,7 +15,8 @@ export * from './src/metadata';
|
||||||
export * from './src/util';
|
export * from './src/util';
|
||||||
export * from './src/di';
|
export * from './src/di';
|
||||||
export {createPlatform, assertPlatform, disposePlatform, getPlatform, coreBootstrap, coreLoadAndBootstrap, PlatformRef, ApplicationRef, enableProdMode, lockRunMode, isDevMode, createPlatformFactory} from './src/application_ref';
|
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/zone';
|
||||||
export * from './src/render';
|
export * from './src/render';
|
||||||
export * from './src/linker';
|
export * from './src/linker';
|
||||||
|
|
|
@ -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; }
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import {Type} from '../src/facade/lang';
|
import {Type} from '../src/facade/lang';
|
||||||
|
|
||||||
|
import {AppInitStatus} from './application_init';
|
||||||
import {ApplicationRef, ApplicationRef_, isDevMode} from './application_ref';
|
import {ApplicationRef, ApplicationRef_, isDevMode} from './application_ref';
|
||||||
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
||||||
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
|
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: [
|
providers: [
|
||||||
ApplicationRef_,
|
ApplicationRef_,
|
||||||
{provide: ApplicationRef, useExisting: ApplicationRef_},
|
{provide: ApplicationRef, useExisting: ApplicationRef_},
|
||||||
|
AppInitStatus,
|
||||||
Compiler,
|
Compiler,
|
||||||
{provide: ComponentResolver, useExisting: Compiler},
|
{provide: ComponentResolver, useExisting: Compiler},
|
||||||
APP_ID_RANDOM_PROVIDER,
|
APP_ID_RANDOM_PROVIDER,
|
||||||
|
|
|
@ -11,7 +11,8 @@ import {ListWrapper} from '../src/facade/collection';
|
||||||
import {BaseException, ExceptionHandler, unimplemented} from '../src/facade/exceptions';
|
import {BaseException, ExceptionHandler, unimplemented} from '../src/facade/exceptions';
|
||||||
import {ConcreteType, Type, isBlank, isPresent, isPromise, stringify} from '../src/facade/lang';
|
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 {ChangeDetectorRef} from './change_detection/change_detector_ref';
|
||||||
import {Console} from './console';
|
import {Console} from './console';
|
||||||
import {Inject, Injectable, Injector, OpaqueToken, Optional, ReflectiveInjector, SkipSelf, forwardRef} from './di';
|
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);
|
exceptionHandler.call(error.error, error.stackTrace);
|
||||||
});
|
});
|
||||||
return _callAndReportToExceptionHandler(exceptionHandler, () => {
|
return _callAndReportToExceptionHandler(exceptionHandler, () => {
|
||||||
const appInits = moduleRef.injector.get(APP_INITIALIZER, null);
|
const initStatus: AppInitStatus = moduleRef.injector.get(AppInitStatus);
|
||||||
const asyncInitPromises: Promise<any>[] = [];
|
return initStatus.donePromise.then(() => {
|
||||||
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();
|
|
||||||
this._moduleDoBootstrap(moduleRef);
|
this._moduleDoBootstrap(moduleRef);
|
||||||
return moduleRef;
|
return moduleRef;
|
||||||
});
|
});
|
||||||
|
@ -430,6 +420,8 @@ export abstract class ApplicationRef {
|
||||||
/**
|
/**
|
||||||
* Returns a promise that resolves when all asynchronous application initializers
|
* Returns a promise that resolves when all asynchronous application initializers
|
||||||
* are done.
|
* are done.
|
||||||
|
*
|
||||||
|
* @deprecated Use the {@link AppInitStatus} class instead.
|
||||||
*/
|
*/
|
||||||
abstract waitForAsyncInitializers(): Promise<any>;
|
abstract waitForAsyncInitializers(): Promise<any>;
|
||||||
|
|
||||||
|
@ -509,13 +501,11 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||||
private _runningTick: boolean = false;
|
private _runningTick: boolean = false;
|
||||||
private _enforceNoNewChanges: boolean = false;
|
private _enforceNoNewChanges: boolean = false;
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_asyncInitDonePromise: PromiseCompleter<any> = PromiseWrapper.completer();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _zone: NgZone, private _console: Console, private _injector: Injector,
|
private _zone: NgZone, private _console: Console, private _injector: Injector,
|
||||||
private _exceptionHandler: ExceptionHandler,
|
private _exceptionHandler: ExceptionHandler,
|
||||||
private _componentFactoryResolver: ComponentFactoryResolver,
|
private _componentFactoryResolver: ComponentFactoryResolver,
|
||||||
|
private _initStatus: AppInitStatus,
|
||||||
@Optional() private _testabilityRegistry: TestabilityRegistry,
|
@Optional() private _testabilityRegistry: TestabilityRegistry,
|
||||||
@Optional() private _testability: Testability) {
|
@Optional() private _testability: Testability) {
|
||||||
super();
|
super();
|
||||||
|
@ -545,11 +535,9 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
asyncInitDone() { this._asyncInitDonePromise.resolve(null); }
|
waitForAsyncInitializers(): Promise<any> { return this._initStatus.donePromise; }
|
||||||
|
|
||||||
waitForAsyncInitializers(): Promise<any> { return this._asyncInitDonePromise.promise; }
|
|
||||||
|
|
||||||
run(callback: Function): any {
|
run(callback: Function): any {
|
||||||
return this._zone.run(
|
return this._zone.run(
|
||||||
|
@ -557,6 +545,10 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap<C>(componentOrFactory: ComponentFactory<C>|ConcreteType<C>): ComponentRef<C> {
|
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(() => {
|
return this.run(() => {
|
||||||
let componentFactory: ComponentFactory<C>;
|
let componentFactory: ComponentFactory<C>;
|
||||||
if (componentOrFactory instanceof ComponentFactory) {
|
if (componentOrFactory instanceof ComponentFactory) {
|
||||||
|
|
|
@ -47,12 +47,6 @@ function _randomChar(): string {
|
||||||
*/
|
*/
|
||||||
export const PLATFORM_INITIALIZER: any = new OpaqueToken('Platform Initializer');
|
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.
|
* All callbacks provided via this token will be called when a component has been bootstrapped.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ import {PromiseCompleter, PromiseWrapper} from '../src/facade/async';
|
||||||
import {ExceptionHandler} from '../src/facade/exception_handler';
|
import {ExceptionHandler} from '../src/facade/exception_handler';
|
||||||
import {BaseException} from '../src/facade/exceptions';
|
import {BaseException} from '../src/facade/exceptions';
|
||||||
import {ConcreteType} from '../src/facade/lang';
|
import {ConcreteType} from '../src/facade/lang';
|
||||||
import {TestBed, async, inject} from '../testing';
|
import {TestBed, async, inject, withModule} from '../testing';
|
||||||
|
|
||||||
import {SpyChangeDetectorRef} from './spies';
|
import {SpyChangeDetectorRef} from './spies';
|
||||||
|
|
||||||
|
@ -133,7 +133,27 @@ export function main() {
|
||||||
const compRef = ref.bootstrap(SomeComponent);
|
const compRef = ref.bootstrap(SomeComponent);
|
||||||
expect(capturedCompRefs).toEqual([compRef]);
|
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}]))
|
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]))
|
||||||
.then(() => expect(false).toBe(true), (e) => {
|
.then(() => expect(false).toBe(true), (e) => {
|
||||||
expect(e).toBe('Test');
|
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(
|
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
|
||||||
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]));
|
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]));
|
||||||
expect(() => defaultPlatform.bootstrapModuleFactory(moduleFactory)).toThrow('Test');
|
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',
|
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
|
||||||
|
|
Loading…
Reference in New Issue