Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix(core): call ngOnDestroy on component level services that are provided using a factory function #28737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions 2 goldens/public-api/forms/forms.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export declare class AbstractFormGroupDirective extends ControlContainer impleme
get control(): FormGroup;
get formDirective(): Form | null;
get path(): string[];
get refCount(): number;
addRef(): void;
ngOnDestroy(): void;
ngOnInit(): void;
}
Expand Down
2 changes: 1 addition & 1 deletion 2 packages/core/src/di/r3_injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ function isExistingProvider(value: SingleProvider): value is ExistingProvider {
return !!(value && (value as ExistingProvider).useExisting);
}

function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
export function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
return !!(value && (value as FactoryProvider).useFactory);
}

Expand Down
59 changes: 40 additions & 19 deletions 59 packages/core/src/render3/di_setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@


import {resolveForwardRef} from '../di/forward_ref';
import {ClassProvider, Provider} from '../di/interface/provider';
import {isClassProvider, isTypeProvider, providerToFactory} from '../di/r3_injector';
import {ClassProvider, Provider, TypeProvider} from '../di/interface/provider';
import {isClassProvider, isFactoryProvider, isTypeProvider, providerToFactory} from '../di/r3_injector';
import {assertDefined} from '../util/assert';

import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from './di';
Expand Down Expand Up @@ -179,6 +179,18 @@ function resolveProvider(
}
}

/**
* Function is used as a destroy hook for Factory Providers. This function is invoked in a context
* of a provider instance, checks whether there is an `ngOnDestroy` function and invokes it if it's
* present.
*/
function ngOnDestroyForFactoryProviders(this: any) {
const onDestroy = this && this.ngOnDestroy;
if (onDestroy) {
onDestroy.call(this);
}
}

/**
* Registers the `ngOnDestroy` hook of a provider, if the provider supports destroy hooks.
* @param tView `TView` in which to register the hook.
Expand All @@ -190,27 +202,36 @@ function resolveProvider(
function registerDestroyHooksIfSupported(
tView: TView, provider: Exclude<Provider, any[]>, contextIndex: number,
indexInFactory?: number) {
const providerIsTypeProvider = isTypeProvider(provider);
if (providerIsTypeProvider || isClassProvider(provider)) {
const prototype = ((provider as ClassProvider).useClass || provider).prototype;
const ngOnDestroy = prototype.ngOnDestroy;
if (ngOnDestroy) {
const hooks = tView.destroyHooks || (tView.destroyHooks = []);
let providerIsClassProvider = false;
let ngOnDestroy;
if (isTypeProvider(provider)) {
ngOnDestroy = (provider as TypeProvider).prototype.ngOnDestroy;
} else if (isClassProvider(provider)) {
providerIsClassProvider = true;
ngOnDestroy = (provider as ClassProvider).useClass.prototype.ngOnDestroy;
} else if (isFactoryProvider(provider)) {
// For factory providers, since we do not have a way to identify whether there is an
// `ngOnDestroy` hook before calling a factory function, we register a special function
// that would be invoked at destroy time and check whether `ngOnDestroy` is present on a
// provider instance (and call the function if it exists).
ngOnDestroy = ngOnDestroyForFactoryProviders;
}
if (ngOnDestroy) {
const hooks = tView.destroyHooks || (tView.destroyHooks = []);

if (!providerIsTypeProvider && ((provider as ClassProvider)).multi) {
ngDevMode &&
assertDefined(
indexInFactory, 'indexInFactory when registering multi factory destroy hook');
const existingCallbacksIndex = hooks.indexOf(contextIndex);
if (providerIsClassProvider && ((provider as ClassProvider)).multi) {
ngDevMode &&
assertDefined(
indexInFactory, 'indexInFactory when registering multi factory destroy hook');
const existingCallbacksIndex = hooks.indexOf(contextIndex);

if (existingCallbacksIndex === -1) {
hooks.push(contextIndex, [indexInFactory, ngOnDestroy]);
} else {
(hooks[existingCallbacksIndex + 1] as DestroyHookData).push(indexInFactory!, ngOnDestroy);
}
if (existingCallbacksIndex === -1) {
hooks.push(contextIndex, [indexInFactory, ngOnDestroy]);
} else {
hooks.push(contextIndex, ngOnDestroy);
(hooks[existingCallbacksIndex + 1] as DestroyHookData).push(indexInFactory!, ngOnDestroy);
}
} else {
hooks.push(contextIndex, ngOnDestroy);
}
}
}
Expand Down
23 changes: 21 additions & 2 deletions 23 packages/core/src/view/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,24 @@ function _createProviderInstance(view: ViewData, def: NodeDef): any {
return createClass(
view, def.parent!, allowPrivateServices, providerDef!.value, providerDef!.deps);
case NodeFlags.TypeFactoryProvider:
return callFactory(
let injectable: any;
injectable = callFactory(
view, def.parent!, allowPrivateServices, providerDef!.value, providerDef!.deps);
// Update def flags if factory injectable has ngOnDestroy function.
if (injectable != null && typeof injectable === 'object' &&
!(def.flags & NodeFlags.OnDestroy) && typeof injectable.ngOnDestroy === 'function') {
def.flags |= NodeFlags.OnDestroy;
view.def.nodeFlags |= NodeFlags.OnDestroy;
let parent: NodeDef|null = def.parent;
if (parent) {
parent.directChildFlags |= NodeFlags.OnDestroy;
while (parent !== null && !(parent.childFlags & NodeFlags.OnDestroy)) {
parent.childFlags |= NodeFlags.OnDestroy;
parent = parent.parent;
}
}
}
return injectable;
case NodeFlags.TypeUseExistingProvider:
return resolveDep(view, def.parent!, allowPrivateServices, providerDef!.deps[0]);
case NodeFlags.TypeValueProvider:
Expand Down Expand Up @@ -573,6 +589,9 @@ function callProviderLifecycles(
provider.ngAfterViewChecked();
}
if (lifecycles & NodeFlags.OnDestroy) {
provider.ngOnDestroy();
const onDestroy = provider && provider.ngOnDestroy;
if (onDestroy) {
onDestroy.call(provider);
}
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.