feat(core): introduce `NgModuleRef.destroy` and call `ngOnDestroy` on all providers

This commit is contained in:
Tobias Bosch 2016-08-02 02:08:10 -07:00
parent c161ed415d
commit ecdaded25f
3 changed files with 104 additions and 3 deletions

View File

@ -8,6 +8,8 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {LifecycleHooks} from '../core_private';
import {CompileDiDependencyMetadata, CompileIdentifierMap, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileIdentifierMap, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Identifiers, identifierToken} from './identifiers'; import {Identifiers, identifierToken} from './identifiers';
@ -71,6 +73,7 @@ class _InjectorBuilder {
private _instances = new CompileIdentifierMap<CompileTokenMetadata, o.Expression>(); private _instances = new CompileIdentifierMap<CompileTokenMetadata, o.Expression>();
private _fields: o.ClassField[] = []; private _fields: o.ClassField[] = [];
private _createStmts: o.Statement[] = []; private _createStmts: o.Statement[] = [];
private _destroyStmts: o.Statement[] = [];
private _getters: o.ClassGetter[] = []; private _getters: o.ClassGetter[] = [];
constructor( constructor(
@ -85,6 +88,9 @@ class _InjectorBuilder {
var instance = this._createProviderProperty( var instance = this._createProviderProperty(
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider, propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
resolvedProvider.eager); resolvedProvider.eager);
if (resolvedProvider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
this._destroyStmts.push(instance.callMethod('ngOnDestroy', []).toStmt());
}
this._instances.add(resolvedProvider.token, instance); this._instances.add(resolvedProvider.token, instance);
} }
@ -108,7 +114,10 @@ class _InjectorBuilder {
new o.FnParam(InjectMethodVars.notFoundResult.name, o.DYNAMIC_TYPE) new o.FnParam(InjectMethodVars.notFoundResult.name, o.DYNAMIC_TYPE)
], ],
getMethodStmts.concat([new o.ReturnStatement(InjectMethodVars.notFoundResult)]), getMethodStmts.concat([new o.ReturnStatement(InjectMethodVars.notFoundResult)]),
o.DYNAMIC_TYPE) o.DYNAMIC_TYPE),
new o.ClassMethod(
'destroyInternal', [], this._destroyStmts
),
]; ];
var ctor = new o.ClassMethod( var ctor = new o.ClassMethod(

View File

@ -7,12 +7,14 @@
*/ */
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector'; import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {unimplemented} from '../facade/exceptions'; import {BaseException, unimplemented} from '../facade/exceptions';
import {ConcreteType} from '../facade/lang'; import {ConcreteType, stringify} from '../facade/lang';
import {ComponentFactory} from './component_factory'; import {ComponentFactory} from './component_factory';
import {CodegenComponentFactoryResolver, ComponentFactoryResolver} from './component_factory_resolver'; import {CodegenComponentFactoryResolver, ComponentFactoryResolver} from './component_factory_resolver';
/** /**
* Represents an instance of an NgModule created via a {@link NgModuleFactory}. * Represents an instance of an NgModule created via a {@link NgModuleFactory}.
* *
@ -37,6 +39,16 @@ export abstract class NgModuleRef<T> {
* The NgModule instance. * The NgModule instance.
*/ */
get instance(): T { return unimplemented(); } get instance(): T { return unimplemented(); }
/**
* Destroys the module instance and all of the data structures associated with it.
*/
abstract destroy(): void;
/**
* Allows to register a callback that will be called when the module is destroyed.
*/
abstract onDestroy(callback: () => void): void;
} }
/** /**
@ -64,6 +76,9 @@ const _UNDEFINED = new Object();
export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolver implements export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolver implements
Injector, Injector,
NgModuleRef<T> { NgModuleRef<T> {
private _destroyListeners: (() => void)[] = [];
private _destroyed: boolean = false;
public instance: T; public instance: T;
constructor(public parent: Injector, factories: ComponentFactory<any>[]) { constructor(public parent: Injector, factories: ComponentFactory<any>[]) {
@ -87,4 +102,18 @@ export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolve
get injector(): Injector { return this; } get injector(): Injector { return this; }
get componentFactoryResolver(): ComponentFactoryResolver { return this; } get componentFactoryResolver(): ComponentFactoryResolver { return this; }
destroy(): void {
if (this._destroyed) {
throw new BaseException(
`This module is already destroyed (${stringify(this.instance.constructor)})`);
}
this._destroyed = true;
this.destroyInternal();
this._destroyListeners.forEach((listener) => listener());
}
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
abstract destroyInternal(): void;
} }

View File

@ -917,6 +917,69 @@ function declareTests({useJit}: {useJit: boolean}) {
}); });
}); });
describe('lifecycle', () => {
it('should instantiate modules eagerly', () => {
let created = false;
@NgModule()
class ImportedModule {
constructor() { created = true; }
}
@NgModule({imports: [ImportedModule]})
class SomeModule {
}
createModule(SomeModule);
expect(created).toBe(true);
});
it('should instantiate providers that are not used by a module lazily', () => {
let created = false;
createInjector([{
provide: 'someToken',
useFactory: () => {
created = true;
return true;
}
}]);
expect(created).toBe(false);
});
it('should support ngOnDestroy on any provider', () => {
let destroyed = false;
class SomeInjectable {
ngOnDestroy() { destroyed = true; }
}
@NgModule({providers: [SomeInjectable]})
class SomeModule {
}
const moduleRef = createModule(SomeModule);
expect(destroyed).toBe(false);
moduleRef.destroy();
expect(destroyed).toBe(true);
});
it('should instantiate providers with lifecycle eagerly', () => {
let created = false;
class SomeInjectable {
constructor() { created = true; }
ngOnDestroy() {}
}
createInjector([SomeInjectable]);
expect(created).toBe(true);
});
});
describe('imported and exported modules', () => { describe('imported and exported modules', () => {
it('should add the providers of imported modules', () => { it('should add the providers of imported modules', () => {
@NgModule({providers: [{provide: 'token1', useValue: 'imported'}]}) @NgModule({providers: [{provide: 'token1', useValue: 'imported'}]})