refactor(core): introduce `APP_BOOTSTRAP_LISTENER` multi provider
Using the `registerBootstrapListener` easily lead to race condition and needed dependencies on `ApplicationRef`. BREAKING CHANGE: - `ApplicationRef.registerBootstrapListener` is deprecated. Provide a multi provider for the new token `APP_BOOTSTRAP_LISTENER` instead.
This commit is contained in:
parent
8e6091de6c
commit
af2e80e068
|
@ -15,7 +15,7 @@ export * from './src/metadata';
|
|||
export * from './src/util';
|
||||
export * from './src/di';
|
||||
export {createPlatform, assertPlatform, disposePlatform, getPlatform, coreBootstrap, coreLoadAndBootstrap, PlatformRef, ApplicationRef, enableProdMode, lockRunMode, isDevMode, createPlatformFactory} from './src/application_ref';
|
||||
export {APP_ID, APP_INITIALIZER, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER} from './src/application_tokens';
|
||||
export {APP_ID, APP_INITIALIZER, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, APP_BOOTSTRAP_LISTENER} from './src/application_tokens';
|
||||
export * from './src/zone';
|
||||
export * from './src/render';
|
||||
export * from './src/linker';
|
||||
|
|
|
@ -11,7 +11,7 @@ import {ListWrapper} from '../src/facade/collection';
|
|||
import {BaseException, ExceptionHandler, unimplemented} from '../src/facade/exceptions';
|
||||
import {ConcreteType, Type, isBlank, isPresent, isPromise} from '../src/facade/lang';
|
||||
|
||||
import {APP_INITIALIZER, PLATFORM_INITIALIZER} from './application_tokens';
|
||||
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, PLATFORM_INITIALIZER} from './application_tokens';
|
||||
import {ChangeDetectorRef} from './change_detection/change_detector_ref';
|
||||
import {Console} from './console';
|
||||
import {Inject, Injectable, Injector, OpaqueToken, Optional, ReflectiveInjector, SkipSelf, forwardRef} from './di';
|
||||
|
@ -400,6 +400,9 @@ export abstract class ApplicationRef {
|
|||
/**
|
||||
* Register a listener to be called each time `bootstrap()` is called to bootstrap
|
||||
* a new root component.
|
||||
*
|
||||
* @deprecated Provide a callback via a multi provider for {@link APP_BOOTSTRAP_LISTENER}
|
||||
* instead.
|
||||
*/
|
||||
abstract registerBootstrapListener(listener: (ref: ComponentRef<any>) => void): void;
|
||||
|
||||
|
@ -503,6 +506,9 @@ export class ApplicationRef_ extends ApplicationRef {
|
|||
this._zone.onMicrotaskEmpty, (_) => { this._zone.run(() => { this.tick(); }); });
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
registerBootstrapListener(listener: (ref: ComponentRef<any>) => void): void {
|
||||
this._bootstrapListeners.push(listener);
|
||||
}
|
||||
|
@ -564,7 +570,11 @@ export class ApplicationRef_ extends ApplicationRef {
|
|||
this._changeDetectorRefs.push(componentRef.changeDetectorRef);
|
||||
this.tick();
|
||||
this._rootComponents.push(componentRef);
|
||||
this._bootstrapListeners.forEach((listener) => listener(componentRef));
|
||||
// Get the listeners lazily to prevent DI cycles.
|
||||
const listeners =
|
||||
<((compRef: ComponentRef<any>) => void)[]>this._injector.get(APP_BOOTSTRAP_LISTENER, [])
|
||||
.concat(this._bootstrapListeners);
|
||||
listeners.forEach((listener) => listener(componentRef));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
|
|
@ -53,6 +53,13 @@ export const PLATFORM_INITIALIZER: any = new OpaqueToken('Platform Initializer')
|
|||
*/
|
||||
export const APP_INITIALIZER: any = new OpaqueToken('Application Initializer');
|
||||
|
||||
/**
|
||||
* All callbacks provided via this token will be called when a component has been bootstrapped.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const APP_BOOTSTRAP_LISTENER = new OpaqueToken('appBootstrapListener');
|
||||
|
||||
/**
|
||||
* A token which indicates the root directory of the application
|
||||
* @experimental
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {APP_INITIALIZER, ChangeDetectorRef, CompilerFactory, Component, Injector, NgModule, PlatformRef} from '@angular/core';
|
||||
import {APP_BOOTSTRAP_LISTENER, 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 {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||
|
@ -57,48 +57,71 @@ export function main() {
|
|||
}
|
||||
|
||||
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 = <any>new SpyChangeDetectorRef();
|
||||
try {
|
||||
ref.registerChangeDetector(cdRef);
|
||||
cdRef.spy('detectChanges').andCallFake(() => ref.tick());
|
||||
expect(() => ref.tick()).toThrowError('ApplicationRef.tick is called recursively');
|
||||
} finally {
|
||||
ref.unregisterChangeDetector(cdRef);
|
||||
}
|
||||
});
|
||||
it('should throw when reentering tick', inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
var cdRef = <any>new SpyChangeDetectorRef();
|
||||
try {
|
||||
ref.registerChangeDetector(cdRef);
|
||||
cdRef.spy('detectChanges').andCallFake(() => ref.tick());
|
||||
expect(() => ref.tick()).toThrowError('ApplicationRef.tick is called recursively');
|
||||
} finally {
|
||||
ref.unregisterChangeDetector(cdRef);
|
||||
}
|
||||
}));
|
||||
|
||||
describe('run', () => {
|
||||
it('should rethrow errors even if the exceptionHandler is not rethrowing', () => {
|
||||
expect(() => ref.run(() => { throw new BaseException('Test'); })).toThrowError('Test');
|
||||
});
|
||||
it('should rethrow errors even if the exceptionHandler is not rethrowing',
|
||||
inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
expect(() => ref.run(() => { throw new BaseException('Test'); })).toThrowError('Test');
|
||||
}));
|
||||
|
||||
it('should return a promise with rejected errors even if the exceptionHandler is not rethrowing',
|
||||
async(() => {
|
||||
async(inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
var promise: Promise<any> = 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<any>[] = [];
|
||||
ref.registerBootstrapListener((compRef) => capturedCompRefs.push(compRef));
|
||||
const compRef = ref.bootstrap(SomeComponent);
|
||||
expect(capturedCompRefs).toEqual([compRef]);
|
||||
it('should be called when a component is bootstrapped',
|
||||
inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
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',
|
||||
inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
ref.registerBootstrapListener((compRef) => capturedCompRefs.push(compRef));
|
||||
const capturedCompRefs: ComponentRef<any>[] = [];
|
||||
const compRef = ref.bootstrap(SomeComponent);
|
||||
expect(capturedCompRefs).toEqual([compRef]);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('APP_BOOTSTRAP_LISTENER', () => {
|
||||
let capturedCompRefs: ComponentRef<any>[];
|
||||
beforeEach(() => {
|
||||
capturedCompRefs = [];
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{
|
||||
provide: APP_BOOTSTRAP_LISTENER,
|
||||
multi: true,
|
||||
useValue: (compRef: any) => { capturedCompRefs.push(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]);
|
||||
});
|
||||
it('should be called when a component is bootstrapped',
|
||||
inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
const compRef = ref.bootstrap(SomeComponent);
|
||||
expect(capturedCompRefs).toEqual([compRef]);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('bootstrapModule', () => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_INITIALIZER, ApplicationRef, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core';
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core';
|
||||
|
||||
import {Routes} from './config';
|
||||
import {Router} from './router';
|
||||
|
@ -53,12 +53,8 @@ export function rootRoute(router: Router): ActivatedRoute {
|
|||
return router.routerState.root;
|
||||
}
|
||||
|
||||
export function setupRouterInitializer(injector: Injector) {
|
||||
return () => {
|
||||
injector.get(ApplicationRef).registerBootstrapListener(() => {
|
||||
injector.get(Router).initialNavigation();
|
||||
});
|
||||
};
|
||||
export function initialRouterNavigation(router: Router) {
|
||||
return () => { router.initialNavigation(); };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,11 +98,19 @@ export function provideRouter(routes: Routes, config: ExtraOptions = {}): any[]
|
|||
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
|
||||
|
||||
// Trigger initial navigation
|
||||
{provide: APP_INITIALIZER, multi: true, useFactory: setupRouterInitializer, deps: [Injector]},
|
||||
{provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader}
|
||||
provideRouterInitializer(), {provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader}
|
||||
];
|
||||
}
|
||||
|
||||
export function provideRouterInitializer() {
|
||||
return {
|
||||
provide: APP_BOOTSTRAP_LISTENER,
|
||||
multi: true,
|
||||
useFactory: initialRouterNavigation,
|
||||
deps: [Router]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Router configuration.
|
||||
*
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
|
||||
import {ApplicationRef, ComponentResolver, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, OpaqueToken, Optional, SystemJsNgModuleLoader} from '@angular/core';
|
||||
|
||||
import {ExtraOptions, ROUTER_CONFIGURATION, provideRouterConfig, provideRoutes, rootRoute, setupRouter} from './common_router_providers';
|
||||
import {ExtraOptions, ROUTER_CONFIGURATION, provideRouterConfig, provideRouterInitializer, provideRoutes, rootRoute, setupRouter} from './common_router_providers';
|
||||
import {Routes} from './config';
|
||||
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||
import {RouterLinkActive} from './directives/router_link_active';
|
||||
|
@ -76,13 +76,6 @@ export const ROUTER_PROVIDERS: any[] = [
|
|||
*/
|
||||
@NgModule({declarations: ROUTER_DIRECTIVES, exports: ROUTER_DIRECTIVES})
|
||||
export class RouterModule {
|
||||
constructor(private injector: Injector, appRef: ApplicationRef) {
|
||||
// do the initialization only once
|
||||
if ((<any>injector).parent.get(RouterModule, null)) return;
|
||||
|
||||
appRef.registerBootstrapListener(() => { injector.get(Router).initialNavigation(); });
|
||||
}
|
||||
|
||||
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: RouterModule,
|
||||
|
@ -94,7 +87,8 @@ export class RouterModule {
|
|||
deps: [
|
||||
PlatformLocation, [new Inject(APP_BASE_HREF), new Optional()], ROUTER_CONFIGURATION
|
||||
]
|
||||
}
|
||||
},
|
||||
provideRouterInitializer()
|
||||
]
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue