refactor(core): fix bootstrapModule
regarding zones and initializers (#10383)
This makes `bootstrapModuleFactory` wait for promises returned by `APP_INITIALIZER`s, also making `bootstrapModuleFactory` async. I.e. now `bootstrapModule` and `bootstrapModuleFactory` behave in the same way. This ensures that all code from module instantiation, to creating `ApplicationRef`s as well as calling `APP_INITIALIZERS` is run in the Angular zone. This also moves the invocation of the initializers from the `ApplicationRef` constructor into the `bootstrapModuleFactory` call, allowing initializers to get a hold of `ApplicationRef` (see #9101). Fixes #9101 Fixes #10363 Fixes #10205
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ObservableWrapper, PromiseWrapper} from '../src/facade/async';
|
||||
import {ObservableWrapper, PromiseCompleter, PromiseWrapper} from '../src/facade/async';
|
||||
import {ListWrapper} from '../src/facade/collection';
|
||||
import {BaseException, ExceptionHandler, unimplemented} from '../src/facade/exceptions';
|
||||
import {ConcreteType, IS_DART, Type, isBlank, isPresent, isPromise} from '../src/facade/lang';
|
||||
@ -27,7 +27,6 @@ import {NgZone, NgZoneError} from './zone/ng_zone';
|
||||
var _devMode: boolean = true;
|
||||
var _runModeLocked: boolean = false;
|
||||
var _platform: PlatformRef;
|
||||
var _inPlatformCreate: boolean = false;
|
||||
|
||||
/**
|
||||
* Disable Angular's development mode, which turns off assertions and other
|
||||
@ -78,19 +77,13 @@ export function isDevMode(): boolean {
|
||||
* @experimental APIs related to application bootstrap are currently under review.
|
||||
*/
|
||||
export function createPlatform(injector: Injector): PlatformRef {
|
||||
if (_inPlatformCreate) {
|
||||
throw new BaseException('Already creating a platform...');
|
||||
}
|
||||
if (isPresent(_platform) && !_platform.disposed) {
|
||||
throw new BaseException(
|
||||
'There can be only one platform. Destroy the previous one to create a new one.');
|
||||
}
|
||||
_inPlatformCreate = true;
|
||||
try {
|
||||
_platform = injector.get(PlatformRef);
|
||||
} finally {
|
||||
_inPlatformCreate = false;
|
||||
}
|
||||
_platform = injector.get(PlatformRef);
|
||||
const inits: Function[] = <Function[]>injector.get(PLATFORM_INITIALIZER, null);
|
||||
if (isPresent(inits)) inits.forEach(init => init());
|
||||
return _platform;
|
||||
}
|
||||
|
||||
@ -218,7 +211,7 @@ export abstract class PlatformRef {
|
||||
*
|
||||
* @experimental APIs related to application bootstrap are currently under review.
|
||||
*/
|
||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): NgModuleRef<M> {
|
||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>> {
|
||||
throw unimplemented();
|
||||
}
|
||||
|
||||
@ -244,8 +237,8 @@ export abstract class PlatformRef {
|
||||
}
|
||||
|
||||
/**
|
||||
*Register a listener to be called when the platform is disposed.
|
||||
*/
|
||||
* Register a listener to be called when the platform is disposed.
|
||||
*/
|
||||
abstract registerDisposeListener(dispose: () => void): void;
|
||||
|
||||
/**
|
||||
@ -262,6 +255,26 @@ export abstract class PlatformRef {
|
||||
get disposed(): boolean { throw unimplemented(); }
|
||||
}
|
||||
|
||||
function _callAndReportToExceptionHandler(
|
||||
exceptionHandler: ExceptionHandler, callback: () => any): any {
|
||||
try {
|
||||
const result = callback();
|
||||
if (isPromise(result)) {
|
||||
return result.catch((e: any) => {
|
||||
exceptionHandler.call(e);
|
||||
// rethrow as the exception handler might not do it
|
||||
throw e;
|
||||
});
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} catch (e) {
|
||||
exceptionHandler.call(e);
|
||||
// rethrow as the exception handler might not do it
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PlatformRef_ extends PlatformRef {
|
||||
/** @internal */
|
||||
@ -271,14 +284,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||
|
||||
private _disposed: boolean = false;
|
||||
|
||||
constructor(private _injector: Injector) {
|
||||
super();
|
||||
if (!_inPlatformCreate) {
|
||||
throw new BaseException('Platforms have to be created via `createPlatform`!');
|
||||
}
|
||||
let inits: Function[] = <Function[]>_injector.get(PLATFORM_INITIALIZER, null);
|
||||
if (isPresent(inits)) inits.forEach(init => init());
|
||||
}
|
||||
constructor(private _injector: Injector) { super(); }
|
||||
|
||||
registerDisposeListener(dispose: () => void): void { this._disposeListeners.push(dispose); }
|
||||
|
||||
@ -286,8 +292,6 @@ export class PlatformRef_ extends PlatformRef {
|
||||
|
||||
get disposed() { return this._disposed; }
|
||||
|
||||
addApplication(appRef: ApplicationRef) { this._applications.push(appRef); }
|
||||
|
||||
dispose(): void {
|
||||
ListWrapper.clone(this._applications).forEach((app) => app.dispose());
|
||||
this._disposeListeners.forEach((dispose) => dispose());
|
||||
@ -297,15 +301,40 @@ export class PlatformRef_ extends PlatformRef {
|
||||
/** @internal */
|
||||
_applicationDisposed(app: ApplicationRef): void { ListWrapper.remove(this._applications, app); }
|
||||
|
||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): NgModuleRef<M> {
|
||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>> {
|
||||
// Note: We need to create the NgZone _before_ we instantiate the module,
|
||||
// as instantiating the module creates some providers eagerly.
|
||||
// So we create a mini parent injector that just contains the new NgZone and
|
||||
// pass that as parent to the NgModuleFactory.
|
||||
const ngZone = new NgZone({enableLongStackTrace: isDevMode()});
|
||||
const ngZoneInjector =
|
||||
ReflectiveInjector.resolveAndCreate([{provide: NgZone, useValue: ngZone}], this.injector);
|
||||
return ngZone.run(() => moduleFactory.create(ngZoneInjector));
|
||||
// Attention: Don't use ApplicationRef.run here,
|
||||
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
|
||||
return ngZone.run(() => {
|
||||
const ngZoneInjector =
|
||||
ReflectiveInjector.resolveAndCreate([{provide: NgZone, useValue: ngZone}], this.injector);
|
||||
const moduleRef = moduleFactory.create(ngZoneInjector);
|
||||
const exceptionHandler: ExceptionHandler = moduleRef.injector.get(ExceptionHandler);
|
||||
ObservableWrapper.subscribe(ngZone.onError, (error: NgZoneError) => {
|
||||
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();
|
||||
return moduleRef;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bootstrapModule<M>(
|
||||
@ -315,11 +344,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||
const compiler = compilerFactory.createCompiler(
|
||||
compilerOptions instanceof Array ? compilerOptions : [compilerOptions]);
|
||||
return compiler.compileModuleAsync(moduleType)
|
||||
.then((moduleFactory) => this.bootstrapModuleFactory(moduleFactory))
|
||||
.then((moduleRef) => {
|
||||
const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
||||
return appRef.waitForAsyncInitializers().then(() => moduleRef);
|
||||
});
|
||||
.then((moduleFactory) => this.bootstrapModuleFactory(moduleFactory));
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,42 +446,17 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
/** @internal */
|
||||
private _enforceNoNewChanges: boolean = false;
|
||||
|
||||
private _asyncInitDonePromise: Promise<any>;
|
||||
private _asyncInitDone: boolean;
|
||||
/** @internal */
|
||||
_asyncInitDonePromise: PromiseCompleter<any> = PromiseWrapper.completer();
|
||||
|
||||
constructor(
|
||||
private _platform: PlatformRef_, private _zone: NgZone, private _console: Console,
|
||||
private _injector: Injector, private _exceptionHandler: ExceptionHandler,
|
||||
private _componentFactoryResolver: ComponentFactoryResolver,
|
||||
@Optional() private _testabilityRegistry: TestabilityRegistry,
|
||||
@Optional() private _testability: Testability,
|
||||
@Optional() @Inject(APP_INITIALIZER) inits: Function[]) {
|
||||
@Optional() private _testability: Testability) {
|
||||
super();
|
||||
this._enforceNoNewChanges = isDevMode();
|
||||
this._asyncInitDonePromise = this.run(() => {
|
||||
var asyncInitResults: Promise<any>[] = [];
|
||||
var asyncInitDonePromise: Promise<any>;
|
||||
if (isPresent(inits)) {
|
||||
for (var i = 0; i < inits.length; i++) {
|
||||
var initResult = inits[i]();
|
||||
if (isPromise(initResult)) {
|
||||
asyncInitResults.push(initResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (asyncInitResults.length > 0) {
|
||||
asyncInitDonePromise =
|
||||
PromiseWrapper.all(asyncInitResults).then((_) => this._asyncInitDone = true);
|
||||
this._asyncInitDone = false;
|
||||
} else {
|
||||
this._asyncInitDone = true;
|
||||
asyncInitDonePromise = PromiseWrapper.resolve(true);
|
||||
}
|
||||
return asyncInitDonePromise;
|
||||
});
|
||||
ObservableWrapper.subscribe(this._zone.onError, (error: NgZoneError) => {
|
||||
this._exceptionHandler.call(error.error, error.stackTrace);
|
||||
});
|
||||
ObservableWrapper.subscribe(
|
||||
this._zone.onMicrotaskEmpty, (_) => { this._zone.run(() => { this.tick(); }); });
|
||||
}
|
||||
@ -475,40 +475,19 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
ListWrapper.remove(this._changeDetectorRefs, changeDetector);
|
||||
}
|
||||
|
||||
waitForAsyncInitializers(): Promise<any> { return this._asyncInitDonePromise; }
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
asyncInitDone() { this._asyncInitDonePromise.resolve(null); }
|
||||
|
||||
waitForAsyncInitializers(): Promise<any> { return this._asyncInitDonePromise.promise; }
|
||||
|
||||
run(callback: Function): any {
|
||||
var result: any;
|
||||
// Note: Don't use zone.runGuarded as we want to know about
|
||||
// the thrown exception!
|
||||
// Note: the completer needs to be created outside
|
||||
// of `zone.run` as Dart swallows rejected promises
|
||||
// via the onError callback of the promise.
|
||||
var completer = PromiseWrapper.completer();
|
||||
this._zone.run(() => {
|
||||
try {
|
||||
result = callback();
|
||||
if (isPromise(result)) {
|
||||
PromiseWrapper.then(
|
||||
result, (ref) => { completer.resolve(ref); },
|
||||
(err, stackTrace) => {
|
||||
completer.reject(err, stackTrace);
|
||||
this._exceptionHandler.call(err, stackTrace);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this._exceptionHandler.call(e, e.stack);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
return isPromise(result) ? completer.promise : result;
|
||||
return this._zone.run(
|
||||
() => _callAndReportToExceptionHandler(this._exceptionHandler, <any>callback));
|
||||
}
|
||||
|
||||
bootstrap<C>(componentOrFactory: ComponentFactory<C>|ConcreteType<C>): ComponentRef<C> {
|
||||
if (!this._asyncInitDone) {
|
||||
throw new BaseException(
|
||||
'Cannot bootstrap as there are still asynchronous initializers running. Wait for them using waitForAsyncInitializers().');
|
||||
}
|
||||
return this.run(() => {
|
||||
let componentFactory: ComponentFactory<C>;
|
||||
if (componentOrFactory instanceof ComponentFactory) {
|
||||
|
Reference in New Issue
Block a user