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:
Tobias Bosch 2016-07-29 06:47:40 -07:00
parent 633c7d1ebe
commit a46437c57d
9 changed files with 223 additions and 211 deletions

View File

@ -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> {

View File

@ -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) {

View File

@ -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) {}

View File

@ -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;
} }
/** /**

View File

@ -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 {

View File

@ -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;
} }
/** /**

View File

@ -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 = [

View File

@ -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;
} }

View File

@ -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;
} }