From a46437c57d0c6a2cffb8256cc9e4f1ab69fef4ea Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 29 Jul 2016 06:47:40 -0700 Subject: [PATCH] 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 --- .../compiler-cli/integrationtest/test/util.ts | 10 +- modules/@angular/core/src/application_ref.ts | 159 ++++++------- .../core/test/application_ref_spec.ts | 212 +++++++++++------- .../router/src/common_router_providers.ts | 17 +- modules/@angular/router/src/router_module.ts | 11 +- .../@angular/upgrade/src/upgrade_adapter.ts | 17 +- .../src/web_workers/router/index_common.ts | 4 - tools/public_api_guard/core/index.d.ts | 2 +- tools/public_api_guard/router/index.d.ts | 2 +- 9 files changed, 223 insertions(+), 211 deletions(-) diff --git a/modules/@angular/compiler-cli/integrationtest/test/util.ts b/modules/@angular/compiler-cli/integrationtest/test/util.ts index 765c64156d..ac476a32ed 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/util.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/util.ts @@ -13,8 +13,16 @@ import {serverPlatform} from '@angular/platform-server'; import {MainModule} from '../src/module'; import {MainModuleNgFactory} from '../src/module.ngfactory'; +let mainModuleRef: NgModuleRef = null; +beforeEach((done) => { + serverPlatform().bootstrapModuleFactory(MainModuleNgFactory).then((moduleRef) => { + mainModuleRef = moduleRef; + done(); + }); +}); + export function createModule(): NgModuleRef { - return serverPlatform().bootstrapModuleFactory(MainModuleNgFactory); + return mainModuleRef; } export function createComponent(comp: {new (...args: any[]): C}): ComponentFixture { diff --git a/modules/@angular/core/src/application_ref.ts b/modules/@angular/core/src/application_ref.ts index ec85d7ad41..f8a7c97d1d 100644 --- a/modules/@angular/core/src/application_ref.ts +++ b/modules/@angular/core/src/application_ref.ts @@ -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[] = 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(moduleFactory: NgModuleFactory): NgModuleRef { + bootstrapModuleFactory(moduleFactory: NgModuleFactory): Promise> { 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[] = _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(moduleFactory: NgModuleFactory): NgModuleRef { + bootstrapModuleFactory(moduleFactory: NgModuleFactory): Promise> { // 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[] = []; + 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( @@ -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; - private _asyncInitDone: boolean; + /** @internal */ + _asyncInitDonePromise: PromiseCompleter = 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[] = []; - var asyncInitDonePromise: Promise; - 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 { return this._asyncInitDonePromise; } + /** + * @internal + */ + asyncInitDone() { this._asyncInitDonePromise.resolve(null); } + + waitForAsyncInitializers(): Promise { 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, callback)); } bootstrap(componentOrFactory: ComponentFactory|ConcreteType): ComponentRef { - 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; if (componentOrFactory instanceof ComponentFactory) { diff --git a/modules/@angular/core/test/application_ref_spec.ts b/modules/@angular/core/test/application_ref_spec.ts index 38f416a099..f1e9a80535 100644 --- a/modules/@angular/core/test/application_ref_spec.ts +++ b/modules/@angular/core/test/application_ref_spec.ts @@ -6,62 +6,63 @@ * 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 {SpyChangeDetectorRef} from './spies'; -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 {APP_INITIALIZER, ChangeDetectorRef, CompilerFactory, Component, Injector, NgModule, PlatformRef} from '@angular/core'; +import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref'; import {Console} from '@angular/core/src/console'; -import {BaseException} from '../src/facade/exceptions'; -import {PromiseWrapper, PromiseCompleter, TimerWrapper} from '../src/facade/async'; -import {ComponentFactory, ComponentRef_, ComponentRef} from '@angular/core/src/linker/component_factory'; +import {ComponentRef} from '@angular/core/src/linker/component_factory'; +import {BrowserModule} from '@angular/platform-browser'; +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 {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() { describe('bootstrap', () => { - var defaultPlatform: PlatformRef; var errorLogger: _ArrayLogger; - var someCompFactory: ComponentFactory; - var appProviders: any[]; + var fakeDoc: Document; beforeEach(() => { + fakeDoc = getDOM().createHtmlDocument(); + const el = getDOM().createElement('comp', fakeDoc); + getDOM().appendChild(fakeDoc.body, el); 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 { - @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 { } 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', () => { + var ref: ApplicationRef_; + beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); }); + beforeEach(inject([ApplicationRef], (_ref: ApplicationRef_) => { ref = _ref; })); + it('should throw when reentering tick', () => { var cdRef = new SpyChangeDetectorRef(); - var ref = createApplication(); try { ref.registerChangeDetector(cdRef); cdRef.spy('detectChanges').andCallFake(() => ref.tick()); @@ -73,29 +74,42 @@ export function main() { describe('run', () => { it('should rethrow errors even if the exceptionHandler is not rethrowing', () => { - var ref = createApplication(); expect(() => ref.run(() => { throw new BaseException('Test'); })).toThrowError('Test'); }); it('should return a promise with rejected errors even if the exceptionHandler is not rethrowing', - inject( - [AsyncTestCompleter, Injector], (async: AsyncTestCompleter, injector: Injector) => { - var ref = createApplication(); - var promise = ref.run(() => PromiseWrapper.reject('Test', null)); - PromiseWrapper.catchError(promise, (e) => { - expect(e).toEqual('Test'); - async.done(); - }); - })); + async(() => { + var promise: Promise = ref.run(() => Promise.reject('Test')); + promise.then(() => expect(false).toBe(true), (e) => { expect(e).toEqual('Test'); }); + })); + }); + + describe('registerBootstrapListener', () => { + it('should be called when a component is bootstrapped', () => { + const capturedCompRefs: ComponentRef[] = []; + 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[] = []; + const compRef = ref.bootstrap(SomeComponent); + expect(capturedCompRefs).toEqual([compRef]); + }); }); }); describe('bootstrapModule', () => { - it('should wait for asynchronous app initializers', - inject([AsyncTestCompleter, Injector], (async: AsyncTestCompleter, injector: Injector) => { + var defaultPlatform: PlatformRef; + beforeEach( + inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; })); + + it('should wait for asynchronous app initializers', async(() => { let completer: PromiseCompleter = PromiseWrapper.completer(); var initializerDone = false; - TimerWrapper.setTimeout(() => { + setTimeout(() => { completer.resolve(true); initializerDone = true; }, 1); @@ -103,24 +117,73 @@ export function main() { defaultPlatform .bootstrapModule(createModule( [{provide: APP_INITIALIZER, useValue: () => completer.promise, multi: true}])) - .then(_ => { - expect(initializerDone).toBe(true); - async.done(); + .then(_ => { expect(initializerDone).toBe(true); }); + })); + + 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', () => { - it('should throw if an APP_INITIIALIZER is not yet resolved', - inject([Injector], (injector: Injector) => { - var app = createApplication([{ - provide: APP_INITIALIZER, - useValue: () => PromiseWrapper.completer().promise, - multi: true - }]); - expect(() => app.bootstrap(someCompFactory)) - .toThrowError( - 'Cannot bootstrap as there are still asynchronous initializers running. Wait for them using waitForAsyncInitializers().'); + describe('bootstrapModuleFactory', () => { + var defaultPlatform: PlatformRef; + beforeEach( + inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; })); + it('should wait for asynchronous app initializers', async(() => { + let completer: PromiseCompleter = PromiseWrapper.completer(); + var initializerDone = false; + setTimeout(() => { + completer.resolve(true); + initializerDone = true; + }, 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(){}; } -class _MockComponentFactory extends ComponentFactory { - constructor(private _compRef: ComponentRef) { super(null, null, null); } - create( - injector: Injector, projectableNodes: any[][] = null, - rootSelectorOrNode: string|any = null): ComponentRef { - return this._compRef; - } -} - -class _MockComponentResolver implements ComponentResolver { - constructor(private _compFactory: ComponentFactory) {} - - resolveComponent(type: Type): Promise> { - return PromiseWrapper.resolve(this._compFactory); - } - clearCache() {} -} - -class _MockComponentRef extends ComponentRef_ { - constructor(private _injector: Injector) { super(null, null); } - get injector(): Injector { return this._injector; } - get changeDetectorRef(): ChangeDetectorRef { return new SpyChangeDetectorRef(); } - onDestroy(cb: Function) {} -} - class _MockConsole implements Console { log(message: string) {} warn(message: string) {} diff --git a/modules/@angular/router/src/common_router_providers.ts b/modules/@angular/router/src/common_router_providers.ts index 328e66afa5..b51f5146c2 100644 --- a/modules/@angular/router/src/common_router_providers.ts +++ b/modules/@angular/router/src/common_router_providers.ts @@ -54,19 +54,10 @@ export function rootRoute(router: Router): ActivatedRoute { return router.routerState.root; } -export function setupRouterInitializer(injector: Injector) { - // https://github.com/angular/angular/issues/9101 - // Delay the router instantiation to avoid circular dependency (ApplicationRef -> - // 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; +export function setupRouterInitializer(injector: Injector, appRef: ApplicationRef) { + return () => { + appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); }); + }; } /** diff --git a/modules/@angular/router/src/router_module.ts b/modules/@angular/router/src/router_module.ts index 8216984384..547a0d09b1 100644 --- a/modules/@angular/router/src/router_module.ts +++ b/modules/@angular/router/src/router_module.ts @@ -76,18 +76,11 @@ export const ROUTER_PROVIDERS: any[] = [ */ @NgModule({declarations: ROUTER_DIRECTIVES, exports: ROUTER_DIRECTIVES}) export class RouterModule { - constructor(private injector: Injector) { + constructor(private injector: Injector, appRef: ApplicationRef) { // do the initialization only once if ((injector).parent.get(RouterModule, null)) return; - setTimeout(() => { - const appRef = injector.get(ApplicationRef); - if (appRef.componentTypes.length == 0) { - appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); }); - } else { - injector.get(Router).initialNavigation(); - } - }, 0); + appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); }); } static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders { diff --git a/modules/@angular/upgrade/src/upgrade_adapter.ts b/modules/@angular/upgrade/src/upgrade_adapter.ts index 35982ef32a..fc2fc68200 100644 --- a/modules/@angular/upgrade/src/upgrade_adapter.ts +++ b/modules/@angular/upgrade/src/upgrade_adapter.ts @@ -6,7 +6,7 @@ * 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 {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @@ -288,10 +288,17 @@ export class UpgradeAdapter { class DynamicModule { } - const compilerFactory: CompilerFactory = platformRef.injector.get(CompilerFactory); - var moduleRef = platformRef.bootstrapModuleFactory( - compilerFactory.createCompiler().compileModuleSync(DynamicModule)); + platformRef.bootstrapModule(DynamicModule).then((moduleRef) => { + ng1Injector = this._afterNg2ModuleBootstrap(moduleRef, upgrade, element, modules, config); + }); + return upgrade; + } + + private _afterNg2ModuleBootstrap( + moduleRef: NgModuleRef, upgrade: UpgradeAdapterRef, element: Element, modules?: any[], + config?: angular.IAngularBootstrapConfig): angular.IInjectorService { const boundCompiler: Compiler = moduleRef.injector.get(Compiler); + var ng1Injector: angular.IInjectorService = null; var applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); var injector: Injector = applicationRef.injector; var ngZone: NgZone = injector.get(NgZone); @@ -398,7 +405,7 @@ export class UpgradeAdapter { } }); }, onError); - return upgrade; + return ng1Injector; } /** diff --git a/modules/playground/src/web_workers/router/index_common.ts b/modules/playground/src/web_workers/router/index_common.ts index 2c1c5a8ae8..d8072d0fc6 100644 --- a/modules/playground/src/web_workers/router/index_common.ts +++ b/modules/playground/src/web_workers/router/index_common.ts @@ -16,10 +16,6 @@ import {HashLocationStrategy, LocationStrategy} from '@angular/common'; @Component({selector: 'app', templateUrl: 'app.html'}) export class App { - constructor(router: Router) { - // this should not be required once web worker bootstrap method can use modules - router.initialNavigation(); - } } export const ROUTES = [ diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index c6f51984cf..b43d3f8ca5 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -979,7 +979,7 @@ export declare abstract class PlatformRef { disposed: boolean; injector: Injector; /** @stable */ bootstrapModule(moduleType: ConcreteType, compilerOptions?: CompilerOptions | CompilerOptions[]): Promise>; - /** @experimental */ bootstrapModuleFactory(moduleFactory: NgModuleFactory): NgModuleRef; + /** @experimental */ bootstrapModuleFactory(moduleFactory: NgModuleFactory): Promise>; abstract dispose(): void; abstract registerDisposeListener(dispose: () => void): void; } diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 14cb17eb95..89ac50745a 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -222,7 +222,7 @@ export declare class RouterLinkWithHref implements OnChanges, OnDestroy { /** @experimental */ export declare class RouterModule { - constructor(injector: Injector); + constructor(injector: Injector, appRef: ApplicationRef); static forChild(routes: Routes): ModuleWithProviders; static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders; }