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/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';
|
||||
|
|
|
@ -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 {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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 {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',
|
||||
|
|
Loading…
Reference in New Issue