fix(ivy): component destroy hook called twice when configured as provider (#28470)

Fixes the `ngOnDestroy` hook on a component or directive being called twice, if the type is also registered as a provider.

This PR resolves FW-1010.

PR Close #28470
This commit is contained in:
Kristiyan Kostadinov
2019-01-31 10:38:43 +01:00
committed by Igor Minar
parent 0ea216b993
commit e1aaa7ec48
12 changed files with 285 additions and 75 deletions

View File

@ -467,6 +467,10 @@ export function isTypeProvider(value: SingleProvider): value is TypeProvider {
return typeof value === 'function';
}
export function isClassProvider(value: SingleProvider): value is ClassProvider {
return !!(value as StaticClassProvider | ClassProvider).useClass;
}
function hasDeps(value: ClassProvider | ConstructorProvider | StaticClassProvider):
value is ClassProvider&{deps: any[]} {
return !!(value as any).deps;

View File

@ -520,10 +520,6 @@ export function getNodeInjectable(
setTNodeAndViewData(tNode, lData);
try {
value = lData[index] = factory.factory(null, tData, lData, tNode);
const tView = lData[TVIEW];
if (value && factory.isProvider && value.ngOnDestroy) {
(tView.destroyHooks || (tView.destroyHooks = [])).push(index, value.ngOnDestroy);
}
} finally {
if (factory.injectImpl) setInjectImplementation(previousInjectImplementation);
setIncludeViewProviders(previousIncludeViewProviders);

View File

@ -8,8 +8,8 @@
import {resolveForwardRef} from '../di/forward_ref';
import {Provider} from '../di/interface/provider';
import {isTypeProvider, providerToFactory} from '../di/r3_injector';
import {ClassProvider, Provider} from '../di/interface/provider';
import {isClassProvider, isTypeProvider, providerToFactory} from '../di/r3_injector';
import {DirectiveDef} from '.';
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from './di';
@ -81,10 +81,19 @@ function resolveProvider(
const cptViewProvidersCount =
tNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
if (isClassProvider(provider) || isTypeProvider(provider)) {
const prototype = ((provider as ClassProvider).useClass || provider).prototype;
const ngOnDestroy = prototype.ngOnDestroy;
if (ngOnDestroy) {
const tView = lView[TVIEW];
(tView.destroyHooks || (tView.destroyHooks = [])).push(tInjectables.length, ngOnDestroy);
}
}
if (isTypeProvider(provider) || !provider.multi) {
// Single provider case: the factory is created and pushed immediately
const factory =
new NodeInjectorFactory(providerFactory, isViewProvider, true, directiveInject);
const factory = new NodeInjectorFactory(providerFactory, isViewProvider, directiveInject);
const existingFactoryIndex = indexOf(
token, tInjectables, isViewProvider ? beginIndex : beginIndex + cptViewProvidersCount,
endIndex);
@ -246,7 +255,7 @@ function multiFactory(
this: NodeInjectorFactory, _: null, tData: TData, lData: LView, tNode: TElementNode) => any,
index: number, isViewProvider: boolean, isComponent: boolean,
f: () => any): NodeInjectorFactory {
const factory = new NodeInjectorFactory(factoryFn, isViewProvider, true, directiveInject);
const factory = new NodeInjectorFactory(factoryFn, isViewProvider, directiveInject);
factory.multi = [];
factory.index = index;
factory.componentProviders = 0;

View File

@ -2067,8 +2067,7 @@ function baseResolveDirective<T>(
tView: TView, viewData: LView, def: DirectiveDef<T>,
directiveFactory: (t: Type<T>| null) => any) {
tView.data.push(def);
const nodeInjectorFactory =
new NodeInjectorFactory(directiveFactory, isComponentDef(def), false, null);
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
tView.blueprint.push(nodeInjectorFactory);
viewData.push(nodeInjectorFactory);
}

View File

@ -235,10 +235,6 @@ export class NodeInjectorFactory {
* Set to `true` if the token is declared in `viewProviders` (or if it is component).
*/
isViewProvider: boolean,
/**
* Set to `true` if the token is a provider, and not a directive.
*/
public isProvider: boolean,
injectImplementation: null|(<T>(token: Type<T>|InjectionToken<T>, flags: InjectFlags) => T)) {
this.canSeeViewProviders = isViewProvider;
this.injectImpl = injectImplementation;

View File

@ -9,9 +9,9 @@
import {ViewEncapsulation} from '../metadata/view';
import {attachPatchData} from './context_discovery';
import {callHooks} from './hooks';
import {LContainer, NATIVE, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {ComponentDef} from './interfaces/definition';
import {NodeInjectorFactory} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
@ -495,8 +495,16 @@ function removeListeners(lView: LView): void {
function executeOnDestroys(view: LView): void {
const tView = view[TVIEW];
let destroyHooks: HookData|null;
if (tView != null && (destroyHooks = tView.destroyHooks) != null) {
callHooks(view, destroyHooks);
for (let i = 0; i < destroyHooks.length; i += 2) {
const context = view[destroyHooks[i] as number];
// Only call the destroy hook if the context has been requested.
if (!(context instanceof NodeInjectorFactory)) {
(destroyHooks[i + 1] as() => void).call(context);
}
}
}
}