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:
parent
633c7d1ebe
commit
a46437c57d
|
@ -13,8 +13,16 @@ import {serverPlatform} from '@angular/platform-server';
|
||||||
import {MainModule} from '../src/module';
|
import {MainModule} from '../src/module';
|
||||||
import {MainModuleNgFactory} from '../src/module.ngfactory';
|
import {MainModuleNgFactory} from '../src/module.ngfactory';
|
||||||
|
|
||||||
|
let mainModuleRef: NgModuleRef<MainModule> = null;
|
||||||
|
beforeEach((done) => {
|
||||||
|
serverPlatform().bootstrapModuleFactory(MainModuleNgFactory).then((moduleRef) => {
|
||||||
|
mainModuleRef = moduleRef;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export function createModule(): NgModuleRef<MainModule> {
|
export function createModule(): NgModuleRef<MainModule> {
|
||||||
return serverPlatform().bootstrapModuleFactory(MainModuleNgFactory);
|
return mainModuleRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createComponent<C>(comp: {new (...args: any[]): C}): ComponentFixture<C> {
|
export function createComponent<C>(comp: {new (...args: any[]): C}): ComponentFixture<C> {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {ListWrapper} from '../src/facade/collection';
|
||||||
import {BaseException, ExceptionHandler, unimplemented} from '../src/facade/exceptions';
|
import {BaseException, ExceptionHandler, unimplemented} from '../src/facade/exceptions';
|
||||||
import {ConcreteType, IS_DART, Type, isBlank, isPresent, isPromise} from '../src/facade/lang';
|
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 _devMode: boolean = true;
|
||||||
var _runModeLocked: boolean = false;
|
var _runModeLocked: boolean = false;
|
||||||
var _platform: PlatformRef;
|
var _platform: PlatformRef;
|
||||||
var _inPlatformCreate: boolean = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable Angular's development mode, which turns off assertions and other
|
* 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.
|
* @experimental APIs related to application bootstrap are currently under review.
|
||||||
*/
|
*/
|
||||||
export function createPlatform(injector: Injector): PlatformRef {
|
export function createPlatform(injector: Injector): PlatformRef {
|
||||||
if (_inPlatformCreate) {
|
|
||||||
throw new BaseException('Already creating a platform...');
|
|
||||||
}
|
|
||||||
if (isPresent(_platform) && !_platform.disposed) {
|
if (isPresent(_platform) && !_platform.disposed) {
|
||||||
throw new BaseException(
|
throw new BaseException(
|
||||||
'There can be only one platform. Destroy the previous one to create a new one.');
|
'There can be only one platform. Destroy the previous one to create a new one.');
|
||||||
}
|
}
|
||||||
_inPlatformCreate = true;
|
_platform = injector.get(PlatformRef);
|
||||||
try {
|
const inits: Function[] = <Function[]>injector.get(PLATFORM_INITIALIZER, null);
|
||||||
_platform = injector.get(PlatformRef);
|
if (isPresent(inits)) inits.forEach(init => init());
|
||||||
} finally {
|
|
||||||
_inPlatformCreate = false;
|
|
||||||
}
|
|
||||||
return _platform;
|
return _platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +211,7 @@ export abstract class PlatformRef {
|
||||||
*
|
*
|
||||||
* @experimental APIs related to application bootstrap are currently under review.
|
* @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();
|
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;
|
abstract registerDisposeListener(dispose: () => void): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -262,6 +255,26 @@ export abstract class PlatformRef {
|
||||||
get disposed(): boolean { throw unimplemented(); }
|
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()
|
@Injectable()
|
||||||
export class PlatformRef_ extends PlatformRef {
|
export class PlatformRef_ extends PlatformRef {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -271,14 +284,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||||
|
|
||||||
private _disposed: boolean = false;
|
private _disposed: boolean = false;
|
||||||
|
|
||||||
constructor(private _injector: Injector) {
|
constructor(private _injector: Injector) { super(); }
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
registerDisposeListener(dispose: () => void): void { this._disposeListeners.push(dispose); }
|
registerDisposeListener(dispose: () => void): void { this._disposeListeners.push(dispose); }
|
||||||
|
|
||||||
|
@ -286,8 +292,6 @@ export class PlatformRef_ extends PlatformRef {
|
||||||
|
|
||||||
get disposed() { return this._disposed; }
|
get disposed() { return this._disposed; }
|
||||||
|
|
||||||
addApplication(appRef: ApplicationRef) { this._applications.push(appRef); }
|
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
ListWrapper.clone(this._applications).forEach((app) => app.dispose());
|
ListWrapper.clone(this._applications).forEach((app) => app.dispose());
|
||||||
this._disposeListeners.forEach((dispose) => dispose());
|
this._disposeListeners.forEach((dispose) => dispose());
|
||||||
|
@ -297,15 +301,40 @@ export class PlatformRef_ extends PlatformRef {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_applicationDisposed(app: ApplicationRef): void { ListWrapper.remove(this._applications, app); }
|
_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,
|
// Note: We need to create the NgZone _before_ we instantiate the module,
|
||||||
// as instantiating the module creates some providers eagerly.
|
// as instantiating the module creates some providers eagerly.
|
||||||
// So we create a mini parent injector that just contains the new NgZone and
|
// So we create a mini parent injector that just contains the new NgZone and
|
||||||
// pass that as parent to the NgModuleFactory.
|
// pass that as parent to the NgModuleFactory.
|
||||||
const ngZone = new NgZone({enableLongStackTrace: isDevMode()});
|
const ngZone = new NgZone({enableLongStackTrace: isDevMode()});
|
||||||
const ngZoneInjector =
|
// Attention: Don't use ApplicationRef.run here,
|
||||||
ReflectiveInjector.resolveAndCreate([{provide: NgZone, useValue: ngZone}], this.injector);
|
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
|
||||||
return ngZone.run(() => moduleFactory.create(ngZoneInjector));
|
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>(
|
bootstrapModule<M>(
|
||||||
|
@ -315,11 +344,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||||
const compiler = compilerFactory.createCompiler(
|
const compiler = compilerFactory.createCompiler(
|
||||||
compilerOptions instanceof Array ? compilerOptions : [compilerOptions]);
|
compilerOptions instanceof Array ? compilerOptions : [compilerOptions]);
|
||||||
return compiler.compileModuleAsync(moduleType)
|
return compiler.compileModuleAsync(moduleType)
|
||||||
.then((moduleFactory) => this.bootstrapModuleFactory(moduleFactory))
|
.then((moduleFactory) => this.bootstrapModuleFactory(moduleFactory));
|
||||||
.then((moduleRef) => {
|
|
||||||
const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
|
||||||
return appRef.waitForAsyncInitializers().then(() => moduleRef);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,42 +446,17 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
private _enforceNoNewChanges: boolean = false;
|
private _enforceNoNewChanges: boolean = false;
|
||||||
|
|
||||||
private _asyncInitDonePromise: Promise<any>;
|
/** @internal */
|
||||||
private _asyncInitDone: boolean;
|
_asyncInitDonePromise: PromiseCompleter<any> = PromiseWrapper.completer();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _platform: PlatformRef_, private _zone: NgZone, private _console: Console,
|
private _platform: PlatformRef_, private _zone: NgZone, private _console: Console,
|
||||||
private _injector: Injector, private _exceptionHandler: ExceptionHandler,
|
private _injector: Injector, private _exceptionHandler: ExceptionHandler,
|
||||||
private _componentFactoryResolver: ComponentFactoryResolver,
|
private _componentFactoryResolver: ComponentFactoryResolver,
|
||||||
@Optional() private _testabilityRegistry: TestabilityRegistry,
|
@Optional() private _testabilityRegistry: TestabilityRegistry,
|
||||||
@Optional() private _testability: Testability,
|
@Optional() private _testability: Testability) {
|
||||||
@Optional() @Inject(APP_INITIALIZER) inits: Function[]) {
|
|
||||||
super();
|
super();
|
||||||
this._enforceNoNewChanges = isDevMode();
|
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(
|
ObservableWrapper.subscribe(
|
||||||
this._zone.onMicrotaskEmpty, (_) => { this._zone.run(() => { this.tick(); }); });
|
this._zone.onMicrotaskEmpty, (_) => { this._zone.run(() => { this.tick(); }); });
|
||||||
}
|
}
|
||||||
|
@ -475,40 +475,19 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||||
ListWrapper.remove(this._changeDetectorRefs, changeDetector);
|
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 {
|
run(callback: Function): any {
|
||||||
var result: any;
|
return this._zone.run(
|
||||||
// Note: Don't use zone.runGuarded as we want to know about
|
() => _callAndReportToExceptionHandler(this._exceptionHandler, <any>callback));
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap<C>(componentOrFactory: ComponentFactory<C>|ConcreteType<C>): ComponentRef<C> {
|
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(() => {
|
return this.run(() => {
|
||||||
let componentFactory: ComponentFactory<C>;
|
let componentFactory: ComponentFactory<C>;
|
||||||
if (componentOrFactory instanceof ComponentFactory) {
|
if (componentOrFactory instanceof ComponentFactory) {
|
||||||
|
|
|
@ -6,62 +6,63 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AsyncTestCompleter, ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, inject,} from '@angular/core/testing/testing_internal';
|
import {APP_INITIALIZER, ChangeDetectorRef, CompilerFactory, Component, Injector, NgModule, PlatformRef} from '@angular/core';
|
||||||
import {SpyChangeDetectorRef} from './spies';
|
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
|
||||||
import {ConcreteType} from '../src/facade/lang';
|
|
||||||
import {ApplicationRef_, ApplicationRef} from '@angular/core/src/application_ref';
|
|
||||||
import {Type, NgModule, CompilerFactory, Injector, APP_INITIALIZER, Component, ReflectiveInjector, PlatformRef, disposePlatform, createPlatformFactory, ComponentResolver, ComponentFactoryResolver, ChangeDetectorRef, ApplicationModule} from '@angular/core';
|
|
||||||
import {platformCoreDynamic} from '@angular/compiler';
|
|
||||||
import {Console} from '@angular/core/src/console';
|
import {Console} from '@angular/core/src/console';
|
||||||
import {BaseException} from '../src/facade/exceptions';
|
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||||
import {PromiseWrapper, PromiseCompleter, TimerWrapper} from '../src/facade/async';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {ComponentFactory, ComponentRef_, ComponentRef} from '@angular/core/src/linker/component_factory';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||||
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
|
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 {ConcreteType} from '../src/facade/lang';
|
||||||
|
import {TestBed, async, inject} from '../testing';
|
||||||
|
|
||||||
|
import {SpyChangeDetectorRef} from './spies';
|
||||||
|
|
||||||
|
@Component({selector: 'comp', template: 'hello'})
|
||||||
|
class SomeComponent {
|
||||||
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('bootstrap', () => {
|
describe('bootstrap', () => {
|
||||||
var defaultPlatform: PlatformRef;
|
|
||||||
var errorLogger: _ArrayLogger;
|
var errorLogger: _ArrayLogger;
|
||||||
var someCompFactory: ComponentFactory<any>;
|
var fakeDoc: Document;
|
||||||
var appProviders: any[];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
fakeDoc = getDOM().createHtmlDocument();
|
||||||
|
const el = getDOM().createElement('comp', fakeDoc);
|
||||||
|
getDOM().appendChild(fakeDoc.body, el);
|
||||||
errorLogger = new _ArrayLogger();
|
errorLogger = new _ArrayLogger();
|
||||||
disposePlatform();
|
|
||||||
defaultPlatform = createPlatformFactory(platformCoreDynamic, 'test')();
|
|
||||||
someCompFactory =
|
|
||||||
new _MockComponentFactory(new _MockComponentRef(ReflectiveInjector.resolveAndCreate([])));
|
|
||||||
appProviders = [
|
|
||||||
{provide: Console, useValue: new _MockConsole()},
|
|
||||||
{provide: ExceptionHandler, useValue: new ExceptionHandler(errorLogger, false)},
|
|
||||||
{provide: ComponentResolver, useValue: new _MockComponentResolver(someCompFactory)}
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => { disposePlatform(); });
|
|
||||||
|
|
||||||
function createModule(providers: any[] = []): ConcreteType<any> {
|
function createModule(providers: any[] = []): ConcreteType<any> {
|
||||||
@NgModule({providers: [appProviders, providers], imports: [ApplicationModule]})
|
@NgModule({
|
||||||
|
providers: [
|
||||||
|
{provide: Console, useValue: new _MockConsole()},
|
||||||
|
{provide: ExceptionHandler, useValue: new ExceptionHandler(errorLogger, false)},
|
||||||
|
{provide: DOCUMENT, useValue: fakeDoc}, providers
|
||||||
|
],
|
||||||
|
imports: [BrowserModule],
|
||||||
|
declarations: [SomeComponent],
|
||||||
|
entryComponents: [SomeComponent]
|
||||||
|
})
|
||||||
class MyModule {
|
class MyModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
return MyModule;
|
return MyModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createApplication(
|
|
||||||
providers: any[] = [], platform: PlatformRef = defaultPlatform): ApplicationRef_ {
|
|
||||||
const compilerFactory: CompilerFactory = platform.injector.get(CompilerFactory);
|
|
||||||
const compiler = compilerFactory.createCompiler();
|
|
||||||
const appInjector =
|
|
||||||
platform.bootstrapModuleFactory(compiler.compileModuleSync(createModule(providers)))
|
|
||||||
.injector;
|
|
||||||
return appInjector.get(ApplicationRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ApplicationRef', () => {
|
describe('ApplicationRef', () => {
|
||||||
|
var ref: ApplicationRef_;
|
||||||
|
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
|
||||||
|
beforeEach(inject([ApplicationRef], (_ref: ApplicationRef_) => { ref = _ref; }));
|
||||||
|
|
||||||
it('should throw when reentering tick', () => {
|
it('should throw when reentering tick', () => {
|
||||||
var cdRef = <any>new SpyChangeDetectorRef();
|
var cdRef = <any>new SpyChangeDetectorRef();
|
||||||
var ref = createApplication();
|
|
||||||
try {
|
try {
|
||||||
ref.registerChangeDetector(cdRef);
|
ref.registerChangeDetector(cdRef);
|
||||||
cdRef.spy('detectChanges').andCallFake(() => ref.tick());
|
cdRef.spy('detectChanges').andCallFake(() => ref.tick());
|
||||||
|
@ -73,29 +74,42 @@ export function main() {
|
||||||
|
|
||||||
describe('run', () => {
|
describe('run', () => {
|
||||||
it('should rethrow errors even if the exceptionHandler is not rethrowing', () => {
|
it('should rethrow errors even if the exceptionHandler is not rethrowing', () => {
|
||||||
var ref = createApplication();
|
|
||||||
expect(() => ref.run(() => { throw new BaseException('Test'); })).toThrowError('Test');
|
expect(() => ref.run(() => { throw new BaseException('Test'); })).toThrowError('Test');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a promise with rejected errors even if the exceptionHandler is not rethrowing',
|
it('should return a promise with rejected errors even if the exceptionHandler is not rethrowing',
|
||||||
inject(
|
async(() => {
|
||||||
[AsyncTestCompleter, Injector], (async: AsyncTestCompleter, injector: Injector) => {
|
var promise: Promise<any> = ref.run(() => Promise.reject('Test'));
|
||||||
var ref = createApplication();
|
promise.then(() => expect(false).toBe(true), (e) => { expect(e).toEqual('Test'); });
|
||||||
var promise = ref.run(() => PromiseWrapper.reject('Test', null));
|
}));
|
||||||
PromiseWrapper.catchError(promise, (e) => {
|
});
|
||||||
expect(e).toEqual('Test');
|
|
||||||
async.done();
|
describe('registerBootstrapListener', () => {
|
||||||
});
|
it('should be called when a component is bootstrapped', () => {
|
||||||
}));
|
const capturedCompRefs: ComponentRef<any>[] = [];
|
||||||
|
ref.registerBootstrapListener((compRef) => capturedCompRefs.push(compRef));
|
||||||
|
const compRef = ref.bootstrap(SomeComponent);
|
||||||
|
expect(capturedCompRefs).toEqual([compRef]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be called immediately when a component was bootstrapped before', () => {
|
||||||
|
ref.registerBootstrapListener((compRef) => capturedCompRefs.push(compRef));
|
||||||
|
const capturedCompRefs: ComponentRef<any>[] = [];
|
||||||
|
const compRef = ref.bootstrap(SomeComponent);
|
||||||
|
expect(capturedCompRefs).toEqual([compRef]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('bootstrapModule', () => {
|
describe('bootstrapModule', () => {
|
||||||
it('should wait for asynchronous app initializers',
|
var defaultPlatform: PlatformRef;
|
||||||
inject([AsyncTestCompleter, Injector], (async: AsyncTestCompleter, injector: Injector) => {
|
beforeEach(
|
||||||
|
inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; }));
|
||||||
|
|
||||||
|
it('should wait for asynchronous app initializers', async(() => {
|
||||||
let completer: PromiseCompleter<any> = PromiseWrapper.completer();
|
let completer: PromiseCompleter<any> = PromiseWrapper.completer();
|
||||||
var initializerDone = false;
|
var initializerDone = false;
|
||||||
TimerWrapper.setTimeout(() => {
|
setTimeout(() => {
|
||||||
completer.resolve(true);
|
completer.resolve(true);
|
||||||
initializerDone = true;
|
initializerDone = true;
|
||||||
}, 1);
|
}, 1);
|
||||||
|
@ -103,24 +117,73 @@ export function main() {
|
||||||
defaultPlatform
|
defaultPlatform
|
||||||
.bootstrapModule(createModule(
|
.bootstrapModule(createModule(
|
||||||
[{provide: APP_INITIALIZER, useValue: () => completer.promise, multi: true}]))
|
[{provide: APP_INITIALIZER, useValue: () => completer.promise, multi: true}]))
|
||||||
.then(_ => {
|
.then(_ => { expect(initializerDone).toBe(true); });
|
||||||
expect(initializerDone).toBe(true);
|
}));
|
||||||
async.done();
|
|
||||||
|
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
|
||||||
|
defaultPlatform
|
||||||
|
.bootstrapModule(createModule(
|
||||||
|
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]))
|
||||||
|
.then(() => expect(false).toBe(true), (e) => {
|
||||||
|
expect(e).toBe('Test');
|
||||||
|
expect(errorLogger.res).toEqual(['EXCEPTION: Test']);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
|
||||||
|
async(() => {
|
||||||
|
defaultPlatform
|
||||||
|
.bootstrapModule(createModule([
|
||||||
|
{provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true}
|
||||||
|
]))
|
||||||
|
.then(() => expect(false).toBe(true), (e) => {
|
||||||
|
expect(e).toBe('Test');
|
||||||
|
expect(errorLogger.res).toEqual(['EXCEPTION: Test']);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ApplicationRef.bootstrap', () => {
|
describe('bootstrapModuleFactory', () => {
|
||||||
it('should throw if an APP_INITIIALIZER is not yet resolved',
|
var defaultPlatform: PlatformRef;
|
||||||
inject([Injector], (injector: Injector) => {
|
beforeEach(
|
||||||
var app = createApplication([{
|
inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; }));
|
||||||
provide: APP_INITIALIZER,
|
it('should wait for asynchronous app initializers', async(() => {
|
||||||
useValue: () => PromiseWrapper.completer().promise,
|
let completer: PromiseCompleter<any> = PromiseWrapper.completer();
|
||||||
multi: true
|
var initializerDone = false;
|
||||||
}]);
|
setTimeout(() => {
|
||||||
expect(() => app.bootstrap(someCompFactory))
|
completer.resolve(true);
|
||||||
.toThrowError(
|
initializerDone = true;
|
||||||
'Cannot bootstrap as there are still asynchronous initializers running. Wait for them using waitForAsyncInitializers().');
|
}, 1);
|
||||||
|
|
||||||
|
const compilerFactory: CompilerFactory =
|
||||||
|
defaultPlatform.injector.get(CompilerFactory, null);
|
||||||
|
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
|
||||||
|
[{provide: APP_INITIALIZER, useValue: () => completer.promise, multi: true}]));
|
||||||
|
defaultPlatform.bootstrapModuleFactory(moduleFactory).then(_ => {
|
||||||
|
expect(initializerDone).toBe(true);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
|
||||||
|
const compilerFactory: CompilerFactory =
|
||||||
|
defaultPlatform.injector.get(CompilerFactory, null);
|
||||||
|
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']);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
|
||||||
|
async(() => {
|
||||||
|
const compilerFactory: CompilerFactory =
|
||||||
|
defaultPlatform.injector.get(CompilerFactory, null);
|
||||||
|
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
|
||||||
|
[{provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true}]));
|
||||||
|
defaultPlatform.bootstrapModuleFactory(moduleFactory)
|
||||||
|
.then(() => expect(false).toBe(true), (e) => {
|
||||||
|
expect(e).toBe('Test');
|
||||||
|
expect(errorLogger.res).toEqual(['EXCEPTION: Test']);
|
||||||
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -138,31 +201,6 @@ class _ArrayLogger {
|
||||||
logGroupEnd(){};
|
logGroupEnd(){};
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MockComponentFactory extends ComponentFactory<any> {
|
|
||||||
constructor(private _compRef: ComponentRef<any>) { super(null, null, null); }
|
|
||||||
create(
|
|
||||||
injector: Injector, projectableNodes: any[][] = null,
|
|
||||||
rootSelectorOrNode: string|any = null): ComponentRef<any> {
|
|
||||||
return this._compRef;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MockComponentResolver implements ComponentResolver {
|
|
||||||
constructor(private _compFactory: ComponentFactory<any>) {}
|
|
||||||
|
|
||||||
resolveComponent(type: Type): Promise<ComponentFactory<any>> {
|
|
||||||
return PromiseWrapper.resolve(this._compFactory);
|
|
||||||
}
|
|
||||||
clearCache() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MockComponentRef extends ComponentRef_<any> {
|
|
||||||
constructor(private _injector: Injector) { super(null, null); }
|
|
||||||
get injector(): Injector { return this._injector; }
|
|
||||||
get changeDetectorRef(): ChangeDetectorRef { return <any>new SpyChangeDetectorRef(); }
|
|
||||||
onDestroy(cb: Function) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MockConsole implements Console {
|
class _MockConsole implements Console {
|
||||||
log(message: string) {}
|
log(message: string) {}
|
||||||
warn(message: string) {}
|
warn(message: string) {}
|
||||||
|
|
|
@ -54,19 +54,10 @@ export function rootRoute(router: Router): ActivatedRoute {
|
||||||
return router.routerState.root;
|
return router.routerState.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupRouterInitializer(injector: Injector) {
|
export function setupRouterInitializer(injector: Injector, appRef: ApplicationRef) {
|
||||||
// https://github.com/angular/angular/issues/9101
|
return () => {
|
||||||
// Delay the router instantiation to avoid circular dependency (ApplicationRef ->
|
appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); });
|
||||||
// APP_INITIALIZER -> Router)
|
};
|
||||||
setTimeout(() => {
|
|
||||||
const appRef = injector.get(ApplicationRef);
|
|
||||||
if (appRef.componentTypes.length == 0) {
|
|
||||||
appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); });
|
|
||||||
} else {
|
|
||||||
injector.get(Router).initialNavigation();
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
return (): any => null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -76,18 +76,11 @@ export const ROUTER_PROVIDERS: any[] = [
|
||||||
*/
|
*/
|
||||||
@NgModule({declarations: ROUTER_DIRECTIVES, exports: ROUTER_DIRECTIVES})
|
@NgModule({declarations: ROUTER_DIRECTIVES, exports: ROUTER_DIRECTIVES})
|
||||||
export class RouterModule {
|
export class RouterModule {
|
||||||
constructor(private injector: Injector) {
|
constructor(private injector: Injector, appRef: ApplicationRef) {
|
||||||
// do the initialization only once
|
// do the initialization only once
|
||||||
if ((<any>injector).parent.get(RouterModule, null)) return;
|
if ((<any>injector).parent.get(RouterModule, null)) return;
|
||||||
|
|
||||||
setTimeout(() => {
|
appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); });
|
||||||
const appRef = injector.get(ApplicationRef);
|
|
||||||
if (appRef.componentTypes.length == 0) {
|
|
||||||
appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); });
|
|
||||||
} else {
|
|
||||||
injector.get(Router).initialNavigation();
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
|
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ApplicationRef, Compiler, CompilerFactory, ComponentFactory, ComponentResolver, Injector, NgModule, NgZone, PlatformRef, Provider, ReflectiveInjector, Testability, Type, provide} from '@angular/core';
|
import {ApplicationRef, Compiler, CompilerFactory, ComponentFactory, ComponentResolver, Injector, NgModule, NgModuleRef, NgZone, PlatformRef, Provider, ReflectiveInjector, Testability, Type, provide} from '@angular/core';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
@ -288,10 +288,17 @@ export class UpgradeAdapter {
|
||||||
class DynamicModule {
|
class DynamicModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
const compilerFactory: CompilerFactory = platformRef.injector.get(CompilerFactory);
|
platformRef.bootstrapModule(DynamicModule).then((moduleRef) => {
|
||||||
var moduleRef = platformRef.bootstrapModuleFactory(
|
ng1Injector = this._afterNg2ModuleBootstrap(moduleRef, upgrade, element, modules, config);
|
||||||
compilerFactory.createCompiler().compileModuleSync(DynamicModule));
|
});
|
||||||
|
return upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _afterNg2ModuleBootstrap(
|
||||||
|
moduleRef: NgModuleRef<any>, upgrade: UpgradeAdapterRef, element: Element, modules?: any[],
|
||||||
|
config?: angular.IAngularBootstrapConfig): angular.IInjectorService {
|
||||||
const boundCompiler: Compiler = moduleRef.injector.get(Compiler);
|
const boundCompiler: Compiler = moduleRef.injector.get(Compiler);
|
||||||
|
var ng1Injector: angular.IInjectorService = null;
|
||||||
var applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
var applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
||||||
var injector: Injector = applicationRef.injector;
|
var injector: Injector = applicationRef.injector;
|
||||||
var ngZone: NgZone = injector.get(NgZone);
|
var ngZone: NgZone = injector.get(NgZone);
|
||||||
|
@ -398,7 +405,7 @@ export class UpgradeAdapter {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, onError);
|
}, onError);
|
||||||
return upgrade;
|
return ng1Injector;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,10 +16,6 @@ import {HashLocationStrategy, LocationStrategy} from '@angular/common';
|
||||||
|
|
||||||
@Component({selector: 'app', templateUrl: 'app.html'})
|
@Component({selector: 'app', templateUrl: 'app.html'})
|
||||||
export class App {
|
export class App {
|
||||||
constructor(router: Router) {
|
|
||||||
// this should not be required once web worker bootstrap method can use modules
|
|
||||||
router.initialNavigation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ROUTES = [
|
export const ROUTES = [
|
||||||
|
|
|
@ -979,7 +979,7 @@ export declare abstract class PlatformRef {
|
||||||
disposed: boolean;
|
disposed: boolean;
|
||||||
injector: Injector;
|
injector: Injector;
|
||||||
/** @stable */ bootstrapModule<M>(moduleType: ConcreteType<M>, compilerOptions?: CompilerOptions | CompilerOptions[]): Promise<NgModuleRef<M>>;
|
/** @stable */ bootstrapModule<M>(moduleType: ConcreteType<M>, compilerOptions?: CompilerOptions | CompilerOptions[]): Promise<NgModuleRef<M>>;
|
||||||
/** @experimental */ bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): NgModuleRef<M>;
|
/** @experimental */ bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>>;
|
||||||
abstract dispose(): void;
|
abstract dispose(): void;
|
||||||
abstract registerDisposeListener(dispose: () => void): void;
|
abstract registerDisposeListener(dispose: () => void): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@ export declare class RouterLinkWithHref implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class RouterModule {
|
export declare class RouterModule {
|
||||||
constructor(injector: Injector);
|
constructor(injector: Injector, appRef: ApplicationRef);
|
||||||
static forChild(routes: Routes): ModuleWithProviders;
|
static forChild(routes: Routes): ModuleWithProviders;
|
||||||
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders;
|
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue