refactor(core): use `ngOnDestroy` in providers

Note about the addition of `beforeEach(fakeAsync(inject(…))))` in some tests:
`ApplicationRef` is now using `ngOnDestroy` and there is eager,
including all of its dependencies which contain `NgZone`.
The additional `fakeAsync` in `beforeEach` ensures that `NgZone`
uses the fake async zone as parent, and not the root zone.

BREAKING CHANGE (via deprecations):
- `ApplicationRef.dispose` is deprecated. Destroy the module that was
   created during bootstrap instead by calling `NgModuleRef.destroy`.
- `AplicationRef.registerDisposeListener` is deprecated.
   Use the `ngOnDestroy` lifecycle hook for providers or
   `NgModuleRef.onDestroy` instead.
- `disposePlatform` is deprecated. Use `destroyPlatform` instead.
- `PlatformRef.dipose()` is deprecated. Use `PlatformRef.destroy()`
   instead.
- `PlatformRef.registerDisposeListener` is deprecated. Use
  `PlatformRef.onDestroy` instead.
- `PlaformRef.diposed` is deprecated. Use `PlatformRef.destroyed`
  instead.
This commit is contained in:
Tobias Bosch 2016-08-02 02:32:27 -07:00
parent ecdaded25f
commit 8e6091de6c
12 changed files with 150 additions and 106 deletions

View File

@ -129,7 +129,7 @@ export function main() {
}))); })));
it('should mark NgForm as submitted on submit event', it('should mark NgForm as submitted on submit event',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
var t = `<div> var t = `<div>
<form #f="ngForm" (ngSubmit)="data=f.submitted"></form> <form #f="ngForm" (ngSubmit)="data=f.submitted"></form>
<span>{{data}}</span> <span>{{data}}</span>
@ -137,9 +137,7 @@ export function main() {
var fixture: ComponentFixture<MyComp8>; var fixture: ComponentFixture<MyComp8>;
tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { fixture = root; });
fixture = root;
});
tick(); tick();
fixture.debugElement.componentInstance.data = false; fixture.debugElement.componentInstance.data = false;
@ -154,7 +152,7 @@ export function main() {
}))); })));
it('should mark NgFormModel as submitted on submit event', it('should mark NgFormModel as submitted on submit event',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
var t = `<div> var t = `<div>
<form #f="ngForm" [ngFormModel]="form" (ngSubmit)="data=f.submitted"></form> <form #f="ngForm" [ngFormModel]="form" (ngSubmit)="data=f.submitted"></form>
<span>{{data}}</span> <span>{{data}}</span>
@ -162,9 +160,7 @@ export function main() {
var fixture: ComponentFixture<MyComp8>; var fixture: ComponentFixture<MyComp8>;
tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { fixture = root; });
fixture = root;
});
tick(); tick();
fixture.debugElement.componentInstance.form = new ControlGroup({}); fixture.debugElement.componentInstance.form = new ControlGroup({});

View File

@ -39,7 +39,7 @@ export function main() {
beforeEach( beforeEach(
() => { TestBed.configureCompiler({providers: [{provide: XHR, useClass: SpyXHR}]}); }); () => { TestBed.configureCompiler({providers: [{provide: XHR, useClass: SpyXHR}]}); });
beforeEach(inject( beforeEach(fakeAsync(inject(
[Compiler, TestComponentBuilder, XHR, DirectiveResolver, Injector], [Compiler, TestComponentBuilder, XHR, DirectiveResolver, Injector],
(_compiler: Compiler, _tcb: TestComponentBuilder, _xhr: SpyXHR, (_compiler: Compiler, _tcb: TestComponentBuilder, _xhr: SpyXHR,
_dirResolver: MockDirectiveResolver, _injector: Injector) => { _dirResolver: MockDirectiveResolver, _injector: Injector) => {
@ -48,7 +48,7 @@ export function main() {
xhr = _xhr; xhr = _xhr;
dirResolver = _dirResolver; dirResolver = _dirResolver;
injector = _injector; injector = _injector;
})); })));
describe('clearCacheFor', () => { describe('clearCacheFor', () => {
it('should support changing the content of a template referenced via templateUrl', it('should support changing the content of a template referenced via templateUrl',

View File

@ -137,11 +137,20 @@ export function assertPlatform(requiredToken: any): PlatformRef {
/** /**
* Dispose the existing platform. * Dispose the existing platform.
* *
* @experimental APIs related to application bootstrap are currently under review. * @deprecated Use `destroyPlatform` instead
*/ */
export function disposePlatform(): void { export function disposePlatform(): void {
if (isPresent(_platform) && !_platform.disposed) { destroyPlatform();
_platform.dispose(); }
/**
* Destroy the existing platform.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function destroyPlatform(): void {
if (isPresent(_platform) && !_platform.destroyed) {
_platform.destroy();
} }
} }
@ -238,9 +247,15 @@ 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.
* @deprecated Use `OnDestroy` instead
*/ */
abstract registerDisposeListener(dispose: () => void): void; abstract registerDisposeListener(dispose: () => void): void;
/**
* Register a listener to be called when the platform is disposed.
*/
abstract onDestroy(callback: () => void): void;
/** /**
* Retrieve the platform {@link Injector}, which is the parent injector for * Retrieve the platform {@link Injector}, which is the parent injector for
* every Angular application on the page and provides singleton providers. * every Angular application on the page and provides singleton providers.
@ -249,10 +264,20 @@ export abstract class PlatformRef {
/** /**
* Destroy the Angular platform and all Angular applications on the page. * Destroy the Angular platform and all Angular applications on the page.
* @deprecated Use `destroy` instead
*/ */
abstract dispose(): void; abstract dispose(): void;
/**
* Destroy the Angular platform and all Angular applications on the page.
*/
abstract destroy(): void;
/**
* @deprecated Use `destroyd` instead
*/
get disposed(): boolean { throw unimplemented(); } get disposed(): boolean { throw unimplemented(); }
get destroyed(): boolean { throw unimplemented(); }
} }
function _callAndReportToExceptionHandler( function _callAndReportToExceptionHandler(
@ -277,29 +302,41 @@ function _callAndReportToExceptionHandler(
@Injectable() @Injectable()
export class PlatformRef_ extends PlatformRef { export class PlatformRef_ extends PlatformRef {
/** @internal */ private _modules: NgModuleRef<any>[] = [];
_applications: ApplicationRef[] = []; private _destroyListeners: Function[] = [];
/** @internal */
_disposeListeners: Function[] = [];
private _disposed: boolean = false; private _destroyed: boolean = false;
constructor(private _injector: Injector) { super(); } constructor(private _injector: Injector) { super(); }
registerDisposeListener(dispose: () => void): void { this._disposeListeners.push(dispose); } /**
* @deprecated
*/
registerDisposeListener(dispose: () => void): void { this.onDestroy(dispose); }
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
get injector(): Injector { return this._injector; } get injector(): Injector { return this._injector; }
get disposed() { return this._disposed; } /**
* @deprecated
*/
get disposed() { return this.destroyed; }
get destroyed() { return this._destroyed; }
dispose(): void { destroy() {
ListWrapper.clone(this._applications).forEach((app) => app.dispose()); if (this._destroyed) {
this._disposeListeners.forEach((dispose) => dispose()); throw new BaseException('The platform is already destroyed!');
this._disposed = true; }
ListWrapper.clone(this._modules).forEach((app) => app.destroy());
this._destroyListeners.forEach((dispose) => dispose());
this._destroyed = true;
} }
/** @internal */ /**
_applicationDisposed(app: ApplicationRef): void { ListWrapper.remove(this._applications, app); } * @deprecated
*/
dispose(): void { this.destroy(); }
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<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,
@ -317,6 +354,7 @@ export class PlatformRef_ extends PlatformRef {
if (!exceptionHandler) { if (!exceptionHandler) {
throw new Error('No ExceptionHandler. Is platform module (BrowserModule) included?'); throw new Error('No ExceptionHandler. Is platform module (BrowserModule) included?');
} }
moduleRef.onDestroy(() => ListWrapper.remove(this._modules, moduleRef));
ObservableWrapper.subscribe(ngZone.onError, (error: NgZoneError) => { ObservableWrapper.subscribe(ngZone.onError, (error: NgZoneError) => {
exceptionHandler.call(error.error, error.stackTrace); exceptionHandler.call(error.error, error.stackTrace);
}); });
@ -367,6 +405,8 @@ export abstract class ApplicationRef {
/** /**
* Register a listener to be called when the application is disposed. * Register a listener to be called when the application is disposed.
*
* @deprecated Use `ngOnDestroy` lifecycle hook or {@link NgModuleRef}.onDestroy.
*/ */
abstract registerDisposeListener(dispose: () => void): void; abstract registerDisposeListener(dispose: () => void): void;
@ -408,6 +448,9 @@ export abstract class ApplicationRef {
/** /**
* Dispose of this application and all of its components. * Dispose of this application and all of its components.
*
* @deprecated Destroy the module that was created during bootstrap instead by calling
* {@link NgModuleRef}.destroy.
*/ */
abstract dispose(): void; abstract dispose(): void;
@ -434,27 +477,23 @@ export class ApplicationRef_ extends ApplicationRef {
/** @internal */ /** @internal */
static _tickScope: WtfScopeFn = wtfCreateScope('ApplicationRef#tick()'); static _tickScope: WtfScopeFn = wtfCreateScope('ApplicationRef#tick()');
/** @internal */
private _bootstrapListeners: Function[] = []; private _bootstrapListeners: Function[] = [];
/** @internal */ /**
* @deprecated
*/
private _disposeListeners: Function[] = []; private _disposeListeners: Function[] = [];
/** @internal */
private _rootComponents: ComponentRef<any>[] = []; private _rootComponents: ComponentRef<any>[] = [];
/** @internal */
private _rootComponentTypes: Type[] = []; private _rootComponentTypes: Type[] = [];
/** @internal */
private _changeDetectorRefs: ChangeDetectorRef[] = []; private _changeDetectorRefs: ChangeDetectorRef[] = [];
/** @internal */
private _runningTick: boolean = false; private _runningTick: boolean = false;
/** @internal */
private _enforceNoNewChanges: boolean = false; private _enforceNoNewChanges: boolean = false;
/** @internal */ /** @internal */
_asyncInitDonePromise: PromiseCompleter<any> = PromiseWrapper.completer(); _asyncInitDonePromise: PromiseCompleter<any> = PromiseWrapper.completer();
constructor( constructor(
private _platform: PlatformRef_, private _zone: NgZone, private _console: Console, private _zone: NgZone, private _console: Console, private _injector: Injector,
private _injector: Injector, private _exceptionHandler: ExceptionHandler, 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) {
@ -468,6 +507,9 @@ export class ApplicationRef_ extends ApplicationRef {
this._bootstrapListeners.push(listener); this._bootstrapListeners.push(listener);
} }
/**
* @deprecated
*/
registerDisposeListener(dispose: () => void): void { this._disposeListeners.push(dispose); } registerDisposeListener(dispose: () => void): void { this._disposeListeners.push(dispose); }
registerChangeDetector(changeDetector: ChangeDetectorRef): void { registerChangeDetector(changeDetector: ChangeDetectorRef): void {
@ -556,12 +598,16 @@ export class ApplicationRef_ extends ApplicationRef {
} }
} }
dispose(): void { ngOnDestroy() {
// TODO(alxhub): Dispose of the NgZone. // TODO(alxhub): Dispose of the NgZone.
ListWrapper.clone(this._rootComponents).forEach((ref) => ref.destroy()); ListWrapper.clone(this._rootComponents).forEach((ref) => ref.destroy());
this._disposeListeners.forEach((dispose) => dispose()); this._disposeListeners.forEach((dispose) => dispose());
this._platform._applicationDisposed(this);
} }
/**
* @deprecated
*/
dispose(): void { this.ngOnDestroy(); }
get componentTypes(): Type[] { return this._rootComponentTypes; } get componentTypes(): Type[] { return this._rootComponentTypes; }
} }

View File

@ -85,7 +85,7 @@ export function main() {
}); });
}); });
beforeEach(inject( beforeEach(fakeAsync(inject(
[TestComponentBuilder, ElementSchemaRegistry, RenderLog, DirectiveLog], [TestComponentBuilder, ElementSchemaRegistry, RenderLog, DirectiveLog],
(_tcb: TestComponentBuilder, _elSchema: MockSchemaRegistry, _renderLog: RenderLog, (_tcb: TestComponentBuilder, _elSchema: MockSchemaRegistry, _renderLog: RenderLog,
_directiveLog: DirectiveLog) => { _directiveLog: DirectiveLog) => {
@ -94,7 +94,7 @@ export function main() {
renderLog = _renderLog; renderLog = _renderLog;
directiveLog = _directiveLog; directiveLog = _directiveLog;
elSchema.existingProperties['someProp'] = true; elSchema.existingProperties['someProp'] = true;
})); })));
describe('expressions', () => { describe('expressions', () => {

View File

@ -268,7 +268,8 @@ export function main() {
beforeEachProviders(() => [{provide: 'appService', useValue: 'appService'}]); beforeEachProviders(() => [{provide: 'appService', useValue: 'appService'}]);
beforeEach(inject([TestComponentBuilder], (_tcb: TestComponentBuilder) => { tcb = _tcb; })); beforeEach(
fakeAsync(inject([TestComponentBuilder], (_tcb: TestComponentBuilder) => { tcb = _tcb; })));
describe('injection', () => { describe('injection', () => {
it('should instantiate directives that have no dependencies', fakeAsync(() => { it('should instantiate directives that have no dependencies', fakeAsync(() => {

View File

@ -116,7 +116,7 @@ export function main() {
}))); })));
it('should mark formGroup as submitted on submit event', it('should mark formGroup as submitted on submit event',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
const t = `<div> const t = `<div>
<form #f="ngForm" [formGroup]="form" (ngSubmit)="data=f.submitted"></form> <form #f="ngForm" [formGroup]="form" (ngSubmit)="data=f.submitted"></form>
<span>{{data}}</span> <span>{{data}}</span>
@ -124,9 +124,7 @@ export function main() {
var fixture: ComponentFixture<MyComp8>; var fixture: ComponentFixture<MyComp8>;
tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { fixture = root; });
fixture = root;
});
tick(); tick();
fixture.debugElement.componentInstance.form = new FormGroup({}); fixture.debugElement.componentInstance.form = new FormGroup({});

View File

@ -104,7 +104,7 @@ export function main() {
}))); })));
it('should mark NgForm as submitted on submit event', it('should mark NgForm as submitted on submit event',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
const t = `<div> const t = `<div>
<form #f="ngForm" (ngSubmit)="data=f.submitted"></form> <form #f="ngForm" (ngSubmit)="data=f.submitted"></form>
<span>{{data}}</span> <span>{{data}}</span>
@ -112,9 +112,7 @@ export function main() {
var fixture: ComponentFixture<MyComp8>; var fixture: ComponentFixture<MyComp8>;
tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((root) => { fixture = root; });
fixture = root;
});
tick(); tick();
fixture.debugElement.componentInstance.data = false; fixture.debugElement.componentInstance.data = false;

View File

@ -548,6 +548,11 @@ export class RootRouter extends Router {
return promise; return promise;
} }
/**
* @internal
*/
ngOnDestroy() { this.dispose(); }
dispose(): void { dispose(): void {
if (isPresent(this._locationSub)) { if (isPresent(this._locationSub)) {
ObservableWrapper.dispose(this._locationSub); ObservableWrapper.dispose(this._locationSub);

View File

@ -23,7 +23,7 @@ export const ROUTER_PROVIDERS_COMMON: any[] = [
RouteRegistry, {provide: LocationStrategy, useClass: PathLocationStrategy}, Location, { RouteRegistry, {provide: LocationStrategy, useClass: PathLocationStrategy}, Location, {
provide: Router, provide: Router,
useFactory: routerFactory, useFactory: routerFactory,
deps: [RouteRegistry, Location, ROUTER_PRIMARY_COMPONENT, ApplicationRef] deps: [RouteRegistry, Location, ROUTER_PRIMARY_COMPONENT]
}, },
{ {
provide: ROUTER_PRIMARY_COMPONENT, provide: ROUTER_PRIMARY_COMPONENT,
@ -33,11 +33,8 @@ export const ROUTER_PROVIDERS_COMMON: any[] = [
]; ];
function routerFactory( function routerFactory(
registry: RouteRegistry, location: Location, primaryComponent: Type, registry: RouteRegistry, location: Location, primaryComponent: Type): RootRouter {
appRef: ApplicationRef): RootRouter { return new RootRouter(registry, location, primaryComponent);
var rootRouter = new RootRouter(registry, location, primaryComponent);
appRef.registerDisposeListener(() => rootRouter.dispose());
return rootRouter;
} }
function routerPrimaryComponentFactory(app: ApplicationRef): Type { function routerPrimaryComponentFactory(app: ApplicationRef): Type {

View File

@ -36,7 +36,6 @@ export function setupRouter(
const componentType = ref.componentTypes[0]; const componentType = ref.componentTypes[0];
const r = new Router( const r = new Router(
componentType, resolver, urlSerializer, outletMap, location, injector, loader, config); componentType, resolver, urlSerializer, outletMap, location, injector, loader, config);
ref.registerDisposeListener(() => r.dispose());
if (opts.enableTracing) { if (opts.enableTracing) {
r.events.subscribe(e => { r.events.subscribe(e => {

View File

@ -196,6 +196,11 @@ export class Router {
this.config = config; this.config = config;
} }
/**
* @internal
*/
ngOnDestroy() { this.dispose(); }
/** /**
* Disposes of the router. * Disposes of the router.
*/ */

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, NgModuleRef, NgZone, PlatformRef, Provider, ReflectiveInjector, Testability, Type, provide} from '@angular/core'; import {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';
@ -299,8 +299,7 @@ export class UpgradeAdapter {
config?: angular.IAngularBootstrapConfig): angular.IInjectorService { 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 ng1Injector: angular.IInjectorService = null;
var applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); var injector: Injector = moduleRef.injector;
var injector: Injector = applicationRef.injector;
var ngZone: NgZone = injector.get(NgZone); var ngZone: NgZone = injector.get(NgZone);
var delayApplyExps: Function[] = []; var delayApplyExps: Function[] = [];
var original$applyFn: Function; var original$applyFn: Function;
@ -400,7 +399,7 @@ export class UpgradeAdapter {
while (delayApplyExps.length) { while (delayApplyExps.length) {
rootScope.$apply(delayApplyExps.shift()); rootScope.$apply(delayApplyExps.shift());
} }
(<any>upgrade)._bootstrapDone(applicationRef, ng1Injector); (<any>upgrade)._bootstrapDone(moduleRef, ng1Injector);
rootScopePrototype = null; rootScopePrototype = null;
} }
}); });
@ -583,13 +582,13 @@ export class UpgradeAdapterRef {
public ng1RootScope: angular.IRootScopeService = null; public ng1RootScope: angular.IRootScopeService = null;
public ng1Injector: angular.IInjectorService = null; public ng1Injector: angular.IInjectorService = null;
public ng2ApplicationRef: ApplicationRef = null; public ng2ModuleRef: NgModuleRef<any> = null;
public ng2Injector: Injector = null; public ng2Injector: Injector = null;
/* @internal */ /* @internal */
private _bootstrapDone(applicationRef: ApplicationRef, ng1Injector: angular.IInjectorService) { private _bootstrapDone(ngModuleRef: NgModuleRef<any>, ng1Injector: angular.IInjectorService) {
this.ng2ApplicationRef = applicationRef; this.ng2ModuleRef = ngModuleRef;
this.ng2Injector = applicationRef.injector; this.ng2Injector = ngModuleRef.injector;
this.ng1Injector = ng1Injector; this.ng1Injector = ng1Injector;
this.ng1RootScope = ng1Injector.get(NG1_ROOT_SCOPE); this.ng1RootScope = ng1Injector.get(NG1_ROOT_SCOPE);
this._readyFn && this._readyFn(this); this._readyFn && this._readyFn(this);
@ -609,6 +608,6 @@ export class UpgradeAdapterRef {
*/ */
public dispose() { public dispose() {
this.ng1Injector.get(NG1_ROOT_SCOPE).$destroy(); this.ng1Injector.get(NG1_ROOT_SCOPE).$destroy();
this.ng2ApplicationRef.dispose(); this.ng2ModuleRef.destroy();
} }
} }