feat(core): introduce `NgModuleRef.destroy` and call `ngOnDestroy` on all providers
This commit is contained in:
parent
c161ed415d
commit
ecdaded25f
|
@ -8,6 +8,8 @@
|
|||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {LifecycleHooks} from '../core_private';
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileIdentifierMap, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {Identifiers, identifierToken} from './identifiers';
|
||||
|
@ -71,6 +73,7 @@ class _InjectorBuilder {
|
|||
private _instances = new CompileIdentifierMap<CompileTokenMetadata, o.Expression>();
|
||||
private _fields: o.ClassField[] = [];
|
||||
private _createStmts: o.Statement[] = [];
|
||||
private _destroyStmts: o.Statement[] = [];
|
||||
private _getters: o.ClassGetter[] = [];
|
||||
|
||||
constructor(
|
||||
|
@ -85,6 +88,9 @@ class _InjectorBuilder {
|
|||
var instance = this._createProviderProperty(
|
||||
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
|
||||
resolvedProvider.eager);
|
||||
if (resolvedProvider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
|
||||
this._destroyStmts.push(instance.callMethod('ngOnDestroy', []).toStmt());
|
||||
}
|
||||
this._instances.add(resolvedProvider.token, instance);
|
||||
}
|
||||
|
||||
|
@ -108,7 +114,10 @@ class _InjectorBuilder {
|
|||
new o.FnParam(InjectMethodVars.notFoundResult.name, o.DYNAMIC_TYPE)
|
||||
],
|
||||
getMethodStmts.concat([new o.ReturnStatement(InjectMethodVars.notFoundResult)]),
|
||||
o.DYNAMIC_TYPE)
|
||||
o.DYNAMIC_TYPE),
|
||||
new o.ClassMethod(
|
||||
'destroyInternal', [], this._destroyStmts
|
||||
),
|
||||
];
|
||||
|
||||
var ctor = new o.ClassMethod(
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
*/
|
||||
|
||||
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
||||
import {unimplemented} from '../facade/exceptions';
|
||||
import {ConcreteType} from '../facade/lang';
|
||||
import {BaseException, unimplemented} from '../facade/exceptions';
|
||||
import {ConcreteType, stringify} from '../facade/lang';
|
||||
|
||||
import {ComponentFactory} from './component_factory';
|
||||
import {CodegenComponentFactoryResolver, ComponentFactoryResolver} from './component_factory_resolver';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Represents an instance of an NgModule created via a {@link NgModuleFactory}.
|
||||
*
|
||||
|
@ -37,6 +39,16 @@ export abstract class NgModuleRef<T> {
|
|||
* The NgModule instance.
|
||||
*/
|
||||
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
|
||||
Injector,
|
||||
NgModuleRef<T> {
|
||||
private _destroyListeners: (() => void)[] = [];
|
||||
private _destroyed: boolean = false;
|
||||
|
||||
public instance: T;
|
||||
|
||||
constructor(public parent: Injector, factories: ComponentFactory<any>[]) {
|
||||
|
@ -87,4 +102,18 @@ export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolve
|
|||
get injector(): Injector { 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;
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
it('should add the providers of imported modules', () => {
|
||||
@NgModule({providers: [{provide: 'token1', useValue: 'imported'}]})
|
||||
|
|
Loading…
Reference in New Issue