fix(core): ngOnDestroy on multi providers called with incorrect context (#35840)

Currently destroy hooks are stored in memory as `[1, hook, 5, hook]` where
the numbers represent the index at which to find the context and `hook` is
the function to be invoked. This breaks down for `multi` providers,
because the value at the index will be an array of providers, resulting in
the hook being invoked with an array of all the multi provider values,
rather than the provider that was destroyed. In ViewEngine `ngOnDestroy`
wasn't being called for `multi` providers at all.

These changes fix the issue by changing the structure of the destroy hooks to `[1, hook, 5, [0, hook, 3, hook]]` where the indexes inside the inner array point to the provider inside of the multi provider array. Note that this is slightly different from the original design which called for the structure to be `[1, hook, 5, [hook, hook]`, because in the process of implementing it, I realized that we wouldn't get passing the correct context if only some of the `multi` providers have `ngOnDestroy` and others don't.

I've run the newly-added `view_destroy_hooks` benchmark against these changes and compared it to master. The difference seems to be insignificant (between 1% and 2% slower).

Fixes #35231.

PR Close #35840
This commit is contained in:
crisbeto 2020-03-03 21:05:26 +01:00 committed by Kara Erickson
parent e145fa13b1
commit 95fc3d4c5c
8 changed files with 579 additions and 296 deletions

View File

@ -12,7 +12,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 2987, "runtime-es2015": 2987,
"main-es2015": 451469, "main-es2015": 452060,
"polyfills-es2015": 52195 "polyfills-es2015": 52195
} }
} }

View File

@ -3,7 +3,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 1485, "runtime-es2015": 1485,
"main-es2015": 141569, "main-es2015": 142073,
"polyfills-es2015": 36657 "polyfills-es2015": 36657
} }
} }
@ -21,7 +21,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 1485, "runtime-es2015": 1485,
"main-es2015": 147647, "main-es2015": 148196,
"polyfills-es2015": 36657 "polyfills-es2015": 36657
} }
} }

View File

@ -10,6 +10,7 @@
import {resolveForwardRef} from '../di/forward_ref'; import {resolveForwardRef} from '../di/forward_ref';
import {ClassProvider, Provider} from '../di/interface/provider'; import {ClassProvider, Provider} from '../di/interface/provider';
import {isClassProvider, isTypeProvider, providerToFactory} from '../di/r3_injector'; import {isClassProvider, isTypeProvider, providerToFactory} from '../di/r3_injector';
import {assertDefined} from '../util/assert';
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from './di'; import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from './di';
import {ɵɵdirectiveInject} from './instructions/all'; import {ɵɵdirectiveInject} from './instructions/all';
@ -17,7 +18,7 @@ import {DirectiveDef} from './interfaces/definition';
import {NodeInjectorFactory} from './interfaces/injector'; import {NodeInjectorFactory} from './interfaces/injector';
import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNodeProviderIndexes} from './interfaces/node'; import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNodeProviderIndexes} from './interfaces/node';
import {isComponentDef} from './interfaces/type_checks'; import {isComponentDef} from './interfaces/type_checks';
import {LView, TData, TVIEW, TView} from './interfaces/view'; import {DestroyHookData, LView, TData, TVIEW, TView} from './interfaces/view';
import {getLView, getPreviousOrParentTNode, getTView} from './state'; import {getLView, getPreviousOrParentTNode, getTView} from './state';
@ -149,7 +150,7 @@ function resolveProvider(
if (!isViewProvider && doesViewProvidersFactoryExist) { if (!isViewProvider && doesViewProvidersFactoryExist) {
lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory; lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
} }
registerDestroyHooksIfSupported(tView, provider, tInjectables.length); registerDestroyHooksIfSupported(tView, provider, tInjectables.length, 0);
tInjectables.push(token); tInjectables.push(token);
tNode.directiveStart++; tNode.directiveStart++;
tNode.directiveEnd++; tNode.directiveEnd++;
@ -160,16 +161,19 @@ function resolveProvider(
lView.push(factory); lView.push(factory);
} else { } else {
// Cases 1.b and 2.b // Cases 1.b and 2.b
registerDestroyHooksIfSupported( const indexInFactory = multiFactoryAdd(
tView, provider, existingProvidersFactoryIndex > -1 ? lInjectablesBlueprint!
existingProvidersFactoryIndex : [isViewProvider ? existingViewProvidersFactoryIndex :
existingViewProvidersFactoryIndex); existingProvidersFactoryIndex],
multiFactoryAdd(
lInjectablesBlueprint ![isViewProvider ? existingViewProvidersFactoryIndex : existingProvidersFactoryIndex],
providerFactory, !isViewProvider && isComponent); providerFactory, !isViewProvider && isComponent);
registerDestroyHooksIfSupported(
tView, provider,
existingProvidersFactoryIndex > -1 ? existingProvidersFactoryIndex :
existingViewProvidersFactoryIndex,
indexInFactory);
} }
if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) { if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) {
lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders !++; lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders!++;
} }
} }
} }
@ -180,28 +184,47 @@ function resolveProvider(
* @param tView `TView` in which to register the hook. * @param tView `TView` in which to register the hook.
* @param provider Provider whose hook should be registered. * @param provider Provider whose hook should be registered.
* @param contextIndex Index under which to find the context for the hook when it's being invoked. * @param contextIndex Index under which to find the context for the hook when it's being invoked.
* @param indexInFactory Only required for `multi` providers. Index of the provider in the multi
* provider factory.
*/ */
function registerDestroyHooksIfSupported( function registerDestroyHooksIfSupported(
tView: TView, provider: Exclude<Provider, any[]>, contextIndex: number) { tView: TView, provider: Exclude<Provider, any[]>, contextIndex: number,
indexInFactory?: number) {
const providerIsTypeProvider = isTypeProvider(provider); const providerIsTypeProvider = isTypeProvider(provider);
if (providerIsTypeProvider || isClassProvider(provider)) { if (providerIsTypeProvider || isClassProvider(provider)) {
const prototype = ((provider as ClassProvider).useClass || provider).prototype; const prototype = ((provider as ClassProvider).useClass || provider).prototype;
const ngOnDestroy = prototype.ngOnDestroy; const ngOnDestroy = prototype.ngOnDestroy;
if (ngOnDestroy) { if (ngOnDestroy) {
(tView.destroyHooks || (tView.destroyHooks = [])).push(contextIndex, 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 (existingCallbacksIndex === -1) {
hooks.push(contextIndex, [indexInFactory, ngOnDestroy]);
} else {
(hooks[existingCallbacksIndex + 1] as DestroyHookData).push(indexInFactory!, ngOnDestroy);
}
} else {
hooks.push(contextIndex, ngOnDestroy);
}
} }
} }
} }
/** /**
* Add a factory in a multi factory. * Add a factory in a multi factory.
* @returns Index at which the factory was inserted.
*/ */
function multiFactoryAdd( function multiFactoryAdd(
multiFactory: NodeInjectorFactory, factory: () => any, isComponentProvider: boolean): void { multiFactory: NodeInjectorFactory, factory: () => any, isComponentProvider: boolean): number {
multiFactory.multi !.push(factory);
if (isComponentProvider) { if (isComponentProvider) {
multiFactory.componentProviders !++; multiFactory.componentProviders!++;
} }
return multiFactory.multi!.push(factory) - 1;
} }
/** /**
@ -220,7 +243,7 @@ function indexOf(item: any, arr: any[], begin: number, end: number) {
function multiProvidersFactoryResolver( function multiProvidersFactoryResolver(
this: NodeInjectorFactory, _: undefined, tData: TData, lData: LView, this: NodeInjectorFactory, _: undefined, tData: TData, lData: LView,
tNode: TDirectiveHostNode): any[] { tNode: TDirectiveHostNode): any[] {
return multiResolve(this.multi !, []); return multiResolve(this.multi!, []);
} }
/** /**
@ -231,12 +254,12 @@ function multiProvidersFactoryResolver(
function multiViewProvidersFactoryResolver( function multiViewProvidersFactoryResolver(
this: NodeInjectorFactory, _: undefined, tData: TData, lView: LView, this: NodeInjectorFactory, _: undefined, tData: TData, lView: LView,
tNode: TDirectiveHostNode): any[] { tNode: TDirectiveHostNode): any[] {
const factories = this.multi !; const factories = this.multi!;
let result: any[]; let result: any[];
if (this.providerFactory) { if (this.providerFactory) {
const componentCount = this.providerFactory.componentProviders !; const componentCount = this.providerFactory.componentProviders!;
const multiProviders = const multiProviders =
getNodeInjectable(lView, lView[TVIEW], this.providerFactory !.index !, tNode); getNodeInjectable(lView, lView[TVIEW], this.providerFactory!.index!, tNode);
// Copy the section of the array which contains `multi` `providers` from the component // Copy the section of the array which contains `multi` `providers` from the component
result = multiProviders.slice(0, componentCount); result = multiProviders.slice(0, componentCount);
// Insert the `viewProvider` instances. // Insert the `viewProvider` instances.
@ -258,7 +281,7 @@ function multiViewProvidersFactoryResolver(
*/ */
function multiResolve(factories: Array<() => any>, result: any[]): any[] { function multiResolve(factories: Array<() => any>, result: any[]): any[] {
for (let i = 0; i < factories.length; i++) { for (let i = 0; i < factories.length; i++) {
const factory = factories[i] !as() => null; const factory = factories[i]! as () => null;
result.push(factory()); result.push(factory());
} }
return result; return result;

View File

@ -19,9 +19,9 @@ import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node'; import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection'; import {SelectorFlags} from '../interfaces/projection';
import {LQueries, TQueries} from '../interfaces/query'; import {LQueries, TQueries} from '../interfaces/query';
import {RComment, RElement, RNode, Renderer3, RendererFactory3} from '../interfaces/renderer'; import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
import {TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate} from '../interfaces/styling'; import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, TViewType, T_HOST} from '../interfaces/view'; import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TVIEW, TView as ITView, TView, TViewType} from '../interfaces/view';
import {attachDebugObject} from '../util/debug_utils'; import {attachDebugObject} from '../util/debug_utils';
import {getLContainerActiveIndex, getTNode, unwrapRNode} from '../util/view_utils'; import {getLContainerActiveIndex, getTNode, unwrapRNode} from '../util/view_utils';
@ -56,9 +56,9 @@ const NG_DEV_MODE = ((typeof ngDevMode === 'undefined' || !!ngDevMode) && initNg
* ``` * ```
*/ */
let LVIEW_COMPONENT_CACHE !: Map<string|null, Array<any>>; let LVIEW_COMPONENT_CACHE!: Map<string|null, Array<any>>;
let LVIEW_EMBEDDED_CACHE !: Map<string|null, Array<any>>; let LVIEW_EMBEDDED_CACHE!: Map<string|null, Array<any>>;
let LVIEW_ROOT !: Array<any>; let LVIEW_ROOT!: Array<any>;
interface TViewDebug extends ITView { interface TViewDebug extends ITView {
type: TViewType; type: TViewType;
@ -75,7 +75,7 @@ export function cloneToLViewFromTViewBlueprint(tView: TView): LView {
return lView.concat(tView.blueprint) as any; return lView.concat(tView.blueprint) as any;
} }
function getLViewToClone(type: TViewType, name: string | null): Array<any> { function getLViewToClone(type: TViewType, name: string|null): Array<any> {
switch (type) { switch (type) {
case TViewType.Root: case TViewType.Root:
if (LVIEW_ROOT === undefined) LVIEW_ROOT = new (createNamedArrayType('LRootView'))(); if (LVIEW_ROOT === undefined) LVIEW_ROOT = new (createNamedArrayType('LRootView'))();
@ -100,7 +100,7 @@ function getLViewToClone(type: TViewType, name: string | null): Array<any> {
throw new Error('unreachable code'); throw new Error('unreachable code');
} }
function nameSuffix(text: string | null | undefined): string { function nameSuffix(text: string|null|undefined): string {
if (text == null) return ''; if (text == null) return '';
const index = text.lastIndexOf('_Template'); const index = text.lastIndexOf('_Template');
return '_' + (index === -1 ? text : text.substr(0, index)); return '_' + (index === -1 ? text : text.substr(0, index));
@ -134,7 +134,7 @@ export const TViewConstructor = class TView implements ITView {
public contentCheckHooks: HookData|null, // public contentCheckHooks: HookData|null, //
public viewHooks: HookData|null, // public viewHooks: HookData|null, //
public viewCheckHooks: HookData|null, // public viewCheckHooks: HookData|null, //
public destroyHooks: HookData|null, // public destroyHooks: DestroyHookData|null, //
public cleanup: any[]|null, // public cleanup: any[]|null, //
public contentQueries: number[]|null, // public contentQueries: number[]|null, //
public components: number[]|null, // public components: number[]|null, //
@ -143,7 +143,7 @@ export const TViewConstructor = class TView implements ITView {
public firstChild: ITNode|null, // public firstChild: ITNode|null, //
public schemas: SchemaMetadata[]|null, // public schemas: SchemaMetadata[]|null, //
public consts: TConstants|null, // public consts: TConstants|null, //
) {} ) {}
get template_(): string { get template_(): string {
const buf: string[] = []; const buf: string[] = [];
@ -183,7 +183,7 @@ class TNode implements ITNode {
public residualClasses: KeyValueArray<any>|undefined|null, // public residualClasses: KeyValueArray<any>|undefined|null, //
public classBindings: TStylingRange, // public classBindings: TStylingRange, //
public styleBindings: TStylingRange, // public styleBindings: TStylingRange, //
) {} ) {}
get type_(): string { get type_(): string {
switch (this.type) { switch (this.type) {
@ -236,8 +236,12 @@ class TNode implements ITNode {
return buf.join(''); return buf.join('');
} }
get styleBindings_(): DebugStyleBindings { return toDebugStyleBinding(this, false); } get styleBindings_(): DebugStyleBindings {
get classBindings_(): DebugStyleBindings { return toDebugStyleBinding(this, true); } return toDebugStyleBinding(this, false);
}
get classBindings_(): DebugStyleBindings {
return toDebugStyleBinding(this, true);
}
} }
export const TNodeDebug = TNode; export const TNodeDebug = TNode;
export type TNodeDebug = TNode; export type TNodeDebug = TNode;
@ -281,17 +285,16 @@ function toDebugStyleBinding(tNode: TNode, isClassBased: boolean): DebugStyleBin
return bindings; return bindings;
} }
function processTNodeChildren(tNode: ITNode | null, buf: string[]) { function processTNodeChildren(tNode: ITNode|null, buf: string[]) {
while (tNode) { while (tNode) {
buf.push((tNode as any as{template_: string}).template_); buf.push((tNode as any as {template_: string}).template_);
tNode = tNode.next; tNode = tNode.next;
} }
} }
const TViewData = NG_DEV_MODE && createNamedArrayType('TViewData') || null !as ArrayConstructor; const TViewData = NG_DEV_MODE && createNamedArrayType('TViewData') || null! as ArrayConstructor;
let TVIEWDATA_EMPTY: let TVIEWDATA_EMPTY: unknown[]; // can't initialize here or it will not be tree shaken, because
unknown[]; // can't initialize here or it will not be tree shaken, because `LView` // `LView` constructor could have side-effects.
// constructor could have side-effects.
/** /**
* This function clones a blueprint and creates TData. * This function clones a blueprint and creates TData.
* *
@ -303,21 +306,21 @@ export function cloneToTViewData(list: any[]): TData {
} }
export const LViewBlueprint = export const LViewBlueprint =
NG_DEV_MODE && createNamedArrayType('LViewBlueprint') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('LViewBlueprint') || null! as ArrayConstructor;
export const MatchesArray = export const MatchesArray =
NG_DEV_MODE && createNamedArrayType('MatchesArray') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('MatchesArray') || null! as ArrayConstructor;
export const TViewComponents = export const TViewComponents =
NG_DEV_MODE && createNamedArrayType('TViewComponents') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('TViewComponents') || null! as ArrayConstructor;
export const TNodeLocalNames = export const TNodeLocalNames =
NG_DEV_MODE && createNamedArrayType('TNodeLocalNames') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('TNodeLocalNames') || null! as ArrayConstructor;
export const TNodeInitialInputs = export const TNodeInitialInputs =
NG_DEV_MODE && createNamedArrayType('TNodeInitialInputs') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('TNodeInitialInputs') || null! as ArrayConstructor;
export const TNodeInitialData = export const TNodeInitialData =
NG_DEV_MODE && createNamedArrayType('TNodeInitialData') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('TNodeInitialData') || null! as ArrayConstructor;
export const LCleanup = export const LCleanup =
NG_DEV_MODE && createNamedArrayType('LCleanup') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('LCleanup') || null! as ArrayConstructor;
export const TCleanup = export const TCleanup =
NG_DEV_MODE && createNamedArrayType('TCleanup') || null !as ArrayConstructor; NG_DEV_MODE && createNamedArrayType('TCleanup') || null! as ArrayConstructor;
@ -330,8 +333,8 @@ export function attachLContainerDebug(lContainer: LContainer) {
} }
export function toDebug(obj: LView): LViewDebug; export function toDebug(obj: LView): LViewDebug;
export function toDebug(obj: LView | null): LViewDebug|null; export function toDebug(obj: LView|null): LViewDebug|null;
export function toDebug(obj: LView | LContainer | null): LViewDebug|LContainerDebug|null; export function toDebug(obj: LView|LContainer|null): LViewDebug|LContainerDebug|null;
export function toDebug(obj: any): any { export function toDebug(obj: any): any {
if (obj) { if (obj) {
const debug = (obj as any).debug; const debug = (obj as any).debug;
@ -390,10 +393,18 @@ export class LViewDebug {
indexWithinInitPhase: flags >> LViewFlags.IndexWithinInitPhaseShift, indexWithinInitPhase: flags >> LViewFlags.IndexWithinInitPhaseShift,
}; };
} }
get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lView[PARENT]); } get parent(): LViewDebug|LContainerDebug|null {
get host(): string|null { return toHtml(this._raw_lView[HOST], true); } return toDebug(this._raw_lView[PARENT]);
get html(): string { return (this.nodes || []).map(node => toHtml(node.native, true)).join(''); } }
get context(): {}|null { return this._raw_lView[CONTEXT]; } get host(): string|null {
return toHtml(this._raw_lView[HOST], true);
}
get html(): string {
return (this.nodes || []).map(node => toHtml(node.native, true)).join('');
}
get context(): {}|null {
return this._raw_lView[CONTEXT];
}
/** /**
* The tree of nodes associated with the current `LView`. The nodes have been normalized into * The tree of nodes associated with the current `LView`. The nodes have been normalized into
* a * a
@ -405,18 +416,42 @@ export class LViewDebug {
return toDebugNodes(tNode, lView); return toDebugNodes(tNode, lView);
} }
get tView(): ITView { return this._raw_lView[TVIEW]; } get tView(): ITView {
get cleanup(): any[]|null { return this._raw_lView[CLEANUP]; } return this._raw_lView[TVIEW];
get injector(): Injector|null { return this._raw_lView[INJECTOR]; } }
get rendererFactory(): RendererFactory3 { return this._raw_lView[RENDERER_FACTORY]; } get cleanup(): any[]|null {
get renderer(): Renderer3 { return this._raw_lView[RENDERER]; } return this._raw_lView[CLEANUP];
get sanitizer(): Sanitizer|null { return this._raw_lView[SANITIZER]; } }
get childHead(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lView[CHILD_HEAD]); } get injector(): Injector|null {
get next(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lView[NEXT]); } return this._raw_lView[INJECTOR];
get childTail(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lView[CHILD_TAIL]); } }
get declarationView(): LViewDebug|null { return toDebug(this._raw_lView[DECLARATION_VIEW]); } get rendererFactory(): RendererFactory3 {
get queries(): LQueries|null { return this._raw_lView[QUERIES]; } return this._raw_lView[RENDERER_FACTORY];
get tHost(): TViewNode|TElementNode|null { return this._raw_lView[T_HOST]; } }
get renderer(): Renderer3 {
return this._raw_lView[RENDERER];
}
get sanitizer(): Sanitizer|null {
return this._raw_lView[SANITIZER];
}
get childHead(): LViewDebug|LContainerDebug|null {
return toDebug(this._raw_lView[CHILD_HEAD]);
}
get next(): LViewDebug|LContainerDebug|null {
return toDebug(this._raw_lView[NEXT]);
}
get childTail(): LViewDebug|LContainerDebug|null {
return toDebug(this._raw_lView[CHILD_TAIL]);
}
get declarationView(): LViewDebug|null {
return toDebug(this._raw_lView[DECLARATION_VIEW]);
}
get queries(): LQueries|null {
return this._raw_lView[QUERIES];
}
get tHost(): TViewNode|TElementNode|null {
return this._raw_lView[T_HOST];
}
/** /**
* Normalized view of child views (and containers) attached at this location. * Normalized view of child views (and containers) attached at this location.
@ -445,7 +480,7 @@ export interface DebugNode {
* @param tNode * @param tNode
* @param lView * @param lView
*/ */
export function toDebugNodes(tNode: ITNode | null, lView: LView): DebugNode[]|null { export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[]|null {
if (tNode) { if (tNode) {
const debugNodes: DebugNode[] = []; const debugNodes: DebugNode[] = [];
let tNodeCursor: ITNode|null = tNode; let tNodeCursor: ITNode|null = tNode;
@ -474,20 +509,32 @@ export function buildDebugNode(tNode: ITNode, lView: LView, nodeIndex: number):
export class LContainerDebug { export class LContainerDebug {
constructor(private readonly _raw_lContainer: LContainer) {} constructor(private readonly _raw_lContainer: LContainer) {}
get activeIndex(): number { return getLContainerActiveIndex(this._raw_lContainer); } get activeIndex(): number {
return getLContainerActiveIndex(this._raw_lContainer);
}
get hasTransplantedViews(): boolean { get hasTransplantedViews(): boolean {
return (this._raw_lContainer[ACTIVE_INDEX] & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) === return (this._raw_lContainer[ACTIVE_INDEX] & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) ===
ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS; ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS;
} }
get views(): LViewDebug[] { get views(): LViewDebug[] {
return this._raw_lContainer.slice(CONTAINER_HEADER_OFFSET) return this._raw_lContainer.slice(CONTAINER_HEADER_OFFSET)
.map(toDebug as(l: LView) => LViewDebug); .map(toDebug as (l: LView) => LViewDebug);
}
get parent(): LViewDebug|LContainerDebug|null {
return toDebug(this._raw_lContainer[PARENT]);
}
get movedViews(): LView[]|null {
return this._raw_lContainer[MOVED_VIEWS];
}
get host(): RElement|RComment|LView {
return this._raw_lContainer[HOST];
}
get native(): RComment {
return this._raw_lContainer[NATIVE];
}
get next() {
return toDebug(this._raw_lContainer[NEXT]);
} }
get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lContainer[PARENT]); }
get movedViews(): LView[]|null { return this._raw_lContainer[MOVED_VIEWS]; }
get host(): RElement|RComment|LView { return this._raw_lContainer[HOST]; }
get native(): RComment { return this._raw_lContainer[NATIVE]; }
get next() { return toDebug(this._raw_lContainer[NEXT]); }
} }
/** /**
@ -508,7 +555,9 @@ export function readLViewValue(value: any): LView|null {
export class I18NDebugItem { export class I18NDebugItem {
[key: string]: any; [key: string]: any;
get tNode() { return getTNode(this._lView[TVIEW], this.nodeIndex); } get tNode() {
return getTNode(this._lView[TVIEW], this.nodeIndex);
}
constructor( constructor(
public __raw_opCode: any, private _lView: LView, public nodeIndex: number, public __raw_opCode: any, private _lView: LView, public nodeIndex: number,
@ -524,15 +573,16 @@ export class I18NDebugItem {
* @param lView The view the opCodes are acting on * @param lView The view the opCodes are acting on
*/ */
export function attachI18nOpCodesDebug( export function attachI18nOpCodesDebug(
mutateOpCodes: I18nMutateOpCodes, updateOpCodes: I18nUpdateOpCodes, icus: TIcu[] | null, mutateOpCodes: I18nMutateOpCodes, updateOpCodes: I18nUpdateOpCodes, icus: TIcu[]|null,
lView: LView) { lView: LView) {
attachDebugObject(mutateOpCodes, new I18nMutateOpCodesDebug(mutateOpCodes, lView)); attachDebugObject(mutateOpCodes, new I18nMutateOpCodesDebug(mutateOpCodes, lView));
attachDebugObject(updateOpCodes, new I18nUpdateOpCodesDebug(updateOpCodes, icus, lView)); attachDebugObject(updateOpCodes, new I18nUpdateOpCodesDebug(updateOpCodes, icus, lView));
if (icus) { if (icus) {
icus.forEach(icu => { icus.forEach(icu => {
icu.create.forEach( icu.create.forEach(icuCase => {
icuCase => { attachDebugObject(icuCase, new I18nMutateOpCodesDebug(icuCase, lView)); }); attachDebugObject(icuCase, new I18nMutateOpCodesDebug(icuCase, lView));
});
icu.update.forEach(icuCase => { icu.update.forEach(icuCase => {
attachDebugObject(icuCase, new I18nUpdateOpCodesDebug(icuCase, icus, lView)); attachDebugObject(icuCase, new I18nUpdateOpCodesDebug(icuCase, icus, lView));
}); });
@ -645,7 +695,7 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug {
if (opCode < 0) { if (opCode < 0) {
// It's a binding index whose value is negative // It's a binding index whose value is negative
// We cannot know the value of the binding so we only show the index // We cannot know the value of the binding so we only show the index
value += `<EFBFBD>${-opCode - 1}<EFBFBD>`; value += `<EFBFBD>${- opCode - 1}<EFBFBD>`;
} else { } else {
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF; const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
let tIcuIndex: number; let tIcuIndex: number;
@ -658,20 +708,23 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug {
__raw_opCode: opCode, __raw_opCode: opCode,
checkBit, checkBit,
type: 'Attr', type: 'Attr',
attrValue: value, attrName, sanitizeFn, attrValue: value,
attrName,
sanitizeFn,
}); });
break; break;
case I18nUpdateOpCode.Text: case I18nUpdateOpCode.Text:
results.push({ results.push({
__raw_opCode: opCode, __raw_opCode: opCode,
checkBit, checkBit,
type: 'Text', nodeIndex, type: 'Text',
nodeIndex,
text: value, text: value,
}); });
break; break;
case I18nUpdateOpCode.IcuSwitch: case I18nUpdateOpCode.IcuSwitch:
tIcuIndex = __raw_opCodes[++j] as number; tIcuIndex = __raw_opCodes[++j] as number;
tIcu = icus ![tIcuIndex]; tIcu = icus![tIcuIndex];
let result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuSwitch'); let result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuSwitch');
result['tIcuIndex'] = tIcuIndex; result['tIcuIndex'] = tIcuIndex;
result['checkBit'] = checkBit; result['checkBit'] = checkBit;
@ -681,7 +734,7 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug {
break; break;
case I18nUpdateOpCode.IcuUpdate: case I18nUpdateOpCode.IcuUpdate:
tIcuIndex = __raw_opCodes[++j] as number; tIcuIndex = __raw_opCodes[++j] as number;
tIcu = icus ![tIcuIndex]; tIcu = icus![tIcuIndex];
result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuUpdate'); result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuUpdate');
result['tIcuIndex'] = tIcuIndex; result['tIcuIndex'] = tIcuIndex;
result['checkBit'] = checkBit; result['checkBit'] = checkBit;
@ -698,4 +751,6 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug {
} }
} }
export interface I18nOpCodesDebug { operations: any[]; } export interface I18nOpCodesDebug {
operations: any[];
}

View File

@ -24,11 +24,11 @@ import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} fr
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from '../interfaces/container'; import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from '../interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector'; import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
import {RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer'; import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization'; import {SanitizerFn} from '../interfaces/sanitization';
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks'; import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view'; import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TVIEW, TView, TViewType} from '../interfaces/view';
import {assertNodeOfPossibleTypes} from '../node_assert'; import {assertNodeOfPossibleTypes} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher'; import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, getTView, leaveView, setBindingIndex, setBindingRootForHostBindings, setCheckNoChangesMode, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state'; import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, getTView, leaveView, setBindingIndex, setBindingRootForHostBindings, setCheckNoChangesMode, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
@ -39,7 +39,7 @@ import {getLViewParent} from '../util/view_traversal_utils';
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, viewAttachedToChangeDetector} from '../util/view_utils'; import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, viewAttachedToChangeDetector} from '../util/view_utils';
import {selectIndexInternal} from './advance'; import {selectIndexInternal} from './advance';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug'; import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug';
@ -129,7 +129,7 @@ function refreshContentQueries(tView: TView, lView: LView): void {
ngDevMode && ngDevMode &&
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined'); assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
setCurrentQueryIndex(queryStartIdx); setCurrentQueryIndex(queryStartIdx);
directiveDef.contentQueries !(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx); directiveDef.contentQueries!(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx);
} }
} }
} }
@ -155,8 +155,7 @@ function renderChildComponents(hostLView: LView, components: number[]): void {
* @param renderer A renderer to use * @param renderer A renderer to use
* @returns the element created * @returns the element created
*/ */
export function elementCreate( export function elementCreate(name: string, renderer: Renderer3, namespace: string|null): RElement {
name: string, renderer: Renderer3, namespace: string | null): RElement {
if (isProceduralRenderer(renderer)) { if (isProceduralRenderer(renderer)) {
return renderer.createElement(name, namespace); return renderer.createElement(name, namespace);
} else { } else {
@ -166,10 +165,9 @@ export function elementCreate(
} }
export function createLView<T>( export function createLView<T>(
parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags, parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null,
host: RElement | null, tHostNode: TViewNode | TElementNode | null, tHostNode: TViewNode|TElementNode|null, rendererFactory?: RendererFactory3|null,
rendererFactory?: RendererFactory3 | null, renderer?: Renderer3 | null, renderer?: Renderer3|null, sanitizer?: Sanitizer|null, injector?: Injector|null): LView {
sanitizer?: Sanitizer | null, injector?: Injector | null): LView {
const lView = const lView =
ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : tView.blueprint.slice() as LView; ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : tView.blueprint.slice() as LView;
lView[HOST] = host; lView[HOST] = host;
@ -177,18 +175,19 @@ export function createLView<T>(
resetPreOrderHookFlags(lView); resetPreOrderHookFlags(lView);
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView; lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
lView[CONTEXT] = context; lView[CONTEXT] = context;
lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY]) !; lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY])!;
ngDevMode && assertDefined(lView[RENDERER_FACTORY], 'RendererFactory is required'); ngDevMode && assertDefined(lView[RENDERER_FACTORY], 'RendererFactory is required');
lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER]) !; lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER])!;
ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required'); ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required');
lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || null !; lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || null!;
lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null; lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null;
lView[T_HOST] = tHostNode; lView[T_HOST] = tHostNode;
ngDevMode && assertEqual( ngDevMode &&
tView.type == TViewType.Embedded ? parentLView !== null : true, true, assertEqual(
'Embedded views must have parentLView'); tView.type == TViewType.Embedded ? parentLView !== null : true, true,
'Embedded views must have parentLView');
lView[DECLARATION_COMPONENT_VIEW] = lView[DECLARATION_COMPONENT_VIEW] =
tView.type == TViewType.Embedded ? parentLView ![DECLARATION_COMPONENT_VIEW] : lView; tView.type == TViewType.Embedded ? parentLView![DECLARATION_COMPONENT_VIEW] : lView;
ngDevMode && attachLViewDebug(lView); ngDevMode && attachLViewDebug(lView);
return lView; return lView;
} }
@ -208,23 +207,23 @@ export function createLView<T>(
* @param attrs Any attrs for the native element, if applicable * @param attrs Any attrs for the native element, if applicable
*/ */
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.Element, tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.Element, name: string|null,
name: string | null, attrs: TAttributes | null): TElementNode; attrs: TAttributes|null): TElementNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.Container, tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.Container,
name: string | null, attrs: TAttributes | null): TContainerNode; name: string|null, attrs: TAttributes|null): TContainerNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.Projection, name: null, tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.Projection, name: null,
attrs: TAttributes | null): TProjectionNode; attrs: TAttributes|null): TProjectionNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.ElementContainer, tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.ElementContainer,
name: string | null, attrs: TAttributes | null): TElementContainerNode; name: string|null, attrs: TAttributes|null): TElementContainerNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.IcuContainer, name: null, tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.IcuContainer, name: null,
attrs: TAttributes | null): TElementContainerNode; attrs: TAttributes|null): TElementContainerNode;
export function getOrCreateTNode( export function getOrCreateTNode(
tView: TView, tHostNode: TNode | null, index: number, type: TNodeType, name: string | null, tView: TView, tHostNode: TNode|null, index: number, type: TNodeType, name: string|null,
attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode& attrs: TAttributes|null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&
TIcuContainerNode { TIcuContainerNode {
// Keep this function short, so that the VM will inline it. // Keep this function short, so that the VM will inline it.
const adjustedIndex = index + HEADER_OFFSET; const adjustedIndex = index + HEADER_OFFSET;
@ -236,8 +235,8 @@ export function getOrCreateTNode(
} }
function createTNodeAtIndex( function createTNodeAtIndex(
tView: TView, tHostNode: TNode | null, adjustedIndex: number, type: TNodeType, tView: TView, tHostNode: TNode|null, adjustedIndex: number, type: TNodeType, name: string|null,
name: string | null, attrs: TAttributes | null) { attrs: TAttributes|null) {
const previousOrParentTNode = getPreviousOrParentTNode(); const previousOrParentTNode = getPreviousOrParentTNode();
const isParent = getIsParent(); const isParent = getIsParent();
const parent = const parent =
@ -267,7 +266,7 @@ function createTNodeAtIndex(
} }
export function assignTViewNodeToLView( export function assignTViewNodeToLView(
tView: TView, tParentNode: TNode | null, index: number, lView: LView): TViewNode { tView: TView, tParentNode: TNode|null, index: number, lView: LView): TViewNode {
// View nodes are not stored in data because they can be added / removed at runtime (which // View nodes are not stored in data because they can be added / removed at runtime (which
// would cause indices to change). Their TNodes are instead stored in tView.node. // would cause indices to change). Their TNodes are instead stored in tView.node.
let tNode = tView.node; let tNode = tView.node;
@ -275,9 +274,9 @@ export function assignTViewNodeToLView(
ngDevMode && tParentNode && ngDevMode && tParentNode &&
assertNodeOfPossibleTypes(tParentNode, TNodeType.Element, TNodeType.Container); assertNodeOfPossibleTypes(tParentNode, TNodeType.Element, TNodeType.Container);
tView.node = tNode = createTNode( tView.node = tNode = createTNode(
tView, tView,
tParentNode as TElementNode | TContainerNode | null, // tParentNode as TElementNode | TContainerNode | null, //
TNodeType.View, index, null, null) as TViewNode; TNodeType.View, index, null, null) as TViewNode;
} }
return lView[T_HOST] = tNode as TViewNode; return lView[T_HOST] = tNode as TViewNode;
@ -294,8 +293,9 @@ export function assignTViewNodeToLView(
* @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0 * @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0
*/ */
export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number) { export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number) {
ngDevMode && assertGreaterThan( ngDevMode &&
numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0'); assertGreaterThan(
numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0');
if (numSlotsToAlloc > 0) { if (numSlotsToAlloc > 0) {
if (tView.firstCreatePass) { if (tView.firstCreatePass) {
for (let i = 0; i < numSlotsToAlloc; i++) { for (let i = 0; i < numSlotsToAlloc; i++) {
@ -365,7 +365,7 @@ export function renderView<T>(tView: TView, lView: LView, context: T): void {
// in case a child component has projected a container. The LContainer needs // in case a child component has projected a container. The LContainer needs
// to exist so the embedded views are properly attached by the container. // to exist so the embedded views are properly attached by the container.
if (tView.staticViewQueries) { if (tView.staticViewQueries) {
executeViewQueryFn(RenderFlags.Update, tView.viewQuery !, context); executeViewQueryFn(RenderFlags.Update, tView.viewQuery!, context);
} }
// Render child component views. // Render child component views.
@ -389,7 +389,7 @@ export function renderView<T>(tView: TView, lView: LView, context: T): void {
* - refreshing child (embedded and component) views. * - refreshing child (embedded and component) views.
*/ */
export function refreshView<T>( export function refreshView<T>(
tView: TView, lView: LView, templateFn: ComponentTemplate<{}>| null, context: T) { tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) {
ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode'); ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode');
const flags = lView[FLAGS]; const flags = lView[FLAGS];
if ((flags & LViewFlags.Destroyed) === LViewFlags.Destroyed) return; if ((flags & LViewFlags.Destroyed) === LViewFlags.Destroyed) return;
@ -505,7 +505,7 @@ export function refreshView<T>(
} }
export function renderComponentOrTemplate<T>( export function renderComponentOrTemplate<T>(
tView: TView, lView: LView, templateFn: ComponentTemplate<{}>| null, context: T) { tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) {
const rendererFactory = lView[RENDERER_FACTORY]; const rendererFactory = lView[RENDERER_FACTORY];
const normalExecutionPath = !getCheckNoChangesMode(); const normalExecutionPath = !getCheckNoChangesMode();
const creationModeIsActive = isCreationMode(lView); const creationModeIsActive = isCreationMode(lView);
@ -618,10 +618,10 @@ export function getOrCreateTComponentView(def: ComponentDef<any>): TView {
* @param consts Constants for this view * @param consts Constants for this view
*/ */
export function createTView( export function createTView(
type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>| null, decls: number, type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>|null, decls: number,
vars: number, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null,
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null, viewQuery: ViewQueriesFunction<any>|null, schemas: SchemaMetadata[]|null,
consts: TConstants | null): TView { consts: TConstants|null): TView {
ngDevMode && ngDevMode.tView++; ngDevMode && ngDevMode.tView++;
const bindingStartIndex = HEADER_OFFSET + decls; const bindingStartIndex = HEADER_OFFSET + decls;
// This length does not yet contain host bindings from child directives because at this point, // This length does not yet contain host bindings from child directives because at this point,
@ -637,7 +637,7 @@ export function createTView(
templateFn, // template: ComponentTemplate<{}>|null, templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null null, // queries: TQueries|null
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null, viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
null !, // node: TViewNode|TElementNode|null, null!, // node: TViewNode|TElementNode|null,
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData, cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number, bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // expandoStartIndex: number, initialViewLength, // expandoStartIndex: number,
@ -652,7 +652,7 @@ export function createTView(
null, // contentCheckHooks: HookData|null, null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null, null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null, null, // viewCheckHooks: HookData|null,
null, // destroyHooks: HookData|null, null, // destroyHooks: DestroyHookData|null,
null, // cleanup: any[]|null, null, // cleanup: any[]|null,
null, // contentQueries: number[]|null, null, // contentQueries: number[]|null,
null, // components: number[]|null, null, // components: number[]|null,
@ -670,7 +670,7 @@ export function createTView(
template: templateFn, template: templateFn,
queries: null, queries: null,
viewQuery: viewQuery, viewQuery: viewQuery,
node: null !, node: null!,
data: blueprint.slice().fill(null, bindingStartIndex), data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex, bindingStartIndex: bindingStartIndex,
expandoStartIndex: initialViewLength, expandoStartIndex: initialViewLength,
@ -711,7 +711,7 @@ function createError(text: string, token: any) {
return new Error(`Renderer: ${text} [${stringifyForError(token)}]`); return new Error(`Renderer: ${text} [${stringifyForError(token)}]`);
} }
function assertHostNodeExists(rElement: RElement, elementOrSelector: RElement | string) { function assertHostNodeExists(rElement: RElement, elementOrSelector: RElement|string) {
if (!rElement) { if (!rElement) {
if (typeof elementOrSelector === 'string') { if (typeof elementOrSelector === 'string') {
throw createError('Host node with selector not found:', elementOrSelector); throw createError('Host node with selector not found:', elementOrSelector);
@ -729,7 +729,7 @@ function assertHostNodeExists(rElement: RElement, elementOrSelector: RElement |
* @param encapsulation View Encapsulation defined for component that requests host element. * @param encapsulation View Encapsulation defined for component that requests host element.
*/ */
export function locateHostElement( export function locateHostElement(
renderer: Renderer3, elementOrSelector: RElement | string, renderer: Renderer3, elementOrSelector: RElement|string,
encapsulation: ViewEncapsulation): RElement { encapsulation: ViewEncapsulation): RElement {
if (isProceduralRenderer(renderer)) { if (isProceduralRenderer(renderer)) {
// When using native Shadow DOM, do not clear host element to allow native slot projection // When using native Shadow DOM, do not clear host element to allow native slot projection
@ -738,7 +738,7 @@ export function locateHostElement(
} }
let rElement = typeof elementOrSelector === 'string' ? let rElement = typeof elementOrSelector === 'string' ?
renderer.querySelector(elementOrSelector) ! : renderer.querySelector(elementOrSelector)! :
elementOrSelector; elementOrSelector;
ngDevMode && assertHostNodeExists(rElement, elementOrSelector); ngDevMode && assertHostNodeExists(rElement, elementOrSelector);
@ -780,7 +780,7 @@ export function storeCleanupFn(tView: TView, lView: LView, cleanupFn: Function):
getLCleanup(lView).push(cleanupFn); getLCleanup(lView).push(cleanupFn);
if (tView.firstCreatePass) { if (tView.firstCreatePass) {
getTViewCleanup(tView).push(lView[CLEANUP] !.length - 1, null); getTViewCleanup(tView).push(lView[CLEANUP]!.length - 1, null);
} }
} }
@ -796,8 +796,8 @@ export function storeCleanupFn(tView: TView, lView: LView, cleanupFn: Function):
* @returns the TNode object * @returns the TNode object
*/ */
export function createTNode( export function createTNode(
tView: TView, tParent: TElementNode | TContainerNode | null, type: TNodeType, tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, adjustedIndex: number,
adjustedIndex: number, tagName: string | null, attrs: TAttributes | null): TNode { tagName: string|null, attrs: TAttributes|null): TNode {
ngDevMode && ngDevMode.tNode++; ngDevMode && ngDevMode.tNode++;
let injectorIndex = tParent ? tParent.injectorIndex : -1; let injectorIndex = tParent ? tParent.injectorIndex : -1;
return ngDevMode ? new TNodeDebug( return ngDevMode ? new TNodeDebug(
@ -866,7 +866,7 @@ export function createTNode(
function generatePropertyAliases( function generatePropertyAliases(
inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number, inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number,
propStore: PropertyAliases | null): PropertyAliases|null { propStore: PropertyAliases|null): PropertyAliases|null {
for (let publicName in inputAliasMap) { for (let publicName in inputAliasMap) {
if (inputAliasMap.hasOwnProperty(publicName)) { if (inputAliasMap.hasOwnProperty(publicName)) {
propStore = propStore === null ? {} : propStore; propStore = propStore === null ? {} : propStore;
@ -942,7 +942,7 @@ function mapPropName(name: string): string {
export function elementPropertyInternal<T>( export function elementPropertyInternal<T>(
tView: TView, tNode: TNode, lView: LView, propName: string, value: T, renderer: Renderer3, tView: TView, tNode: TNode, lView: LView, propName: string, value: T, renderer: Renderer3,
sanitizer: SanitizerFn | null | undefined, nativeOnly: boolean): void { sanitizer: SanitizerFn|null|undefined, nativeOnly: boolean): void {
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
const element = getNativeByTNode(tNode, lView) as RElement | RComment; const element = getNativeByTNode(tNode, lView) as RElement | RComment;
let inputData = tNode.inputs; let inputData = tNode.inputs;
@ -994,7 +994,7 @@ function markDirtyIfOnPush(lView: LView, viewIndex: number): void {
} }
function setNgReflectProperty( function setNgReflectProperty(
lView: LView, element: RElement | RComment, type: TNodeType, attrName: string, value: any) { lView: LView, element: RElement|RComment, type: TNodeType, attrName: string, value: any) {
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
attrName = normalizeDebugBindingName(attrName); attrName = normalizeDebugBindingName(attrName);
const debugValue = normalizeDebugBindingValue(value); const debugValue = normalizeDebugBindingValue(value);
@ -1018,7 +1018,7 @@ function setNgReflectProperty(
} }
export function setNgReflectProperties( export function setNgReflectProperties(
lView: LView, element: RElement | RComment, type: TNodeType, dataValue: PropertyAliasValue, lView: LView, element: RElement|RComment, type: TNodeType, dataValue: PropertyAliasValue,
value: any) { value: any) {
if (type === TNodeType.Element || type === TNodeType.Container) { if (type === TNodeType.Element || type === TNodeType.Container) {
/** /**
@ -1036,7 +1036,7 @@ export function setNgReflectProperties(
} }
function validateProperty( function validateProperty(
tView: TView, lView: LView, element: RElement | RComment, propName: string, tView: TView, lView: LView, element: RElement|RComment, propName: string,
tNode: TNode): boolean { tNode: TNode): boolean {
// The property is considered valid if the element matches the schema, it exists on the element // The property is considered valid if the element matches the schema, it exists on the element
// or it is synthetic, and we are in a browser context (web worker nodes should be skipped). // or it is synthetic, and we are in a browser context (web worker nodes should be skipped).
@ -1050,7 +1050,7 @@ function validateProperty(
return typeof Node === 'undefined' || Node === null || !(element instanceof Node); return typeof Node === 'undefined' || Node === null || !(element instanceof Node);
} }
export function matchingSchemas(tView: TView, lView: LView, tagName: string | null): boolean { export function matchingSchemas(tView: TView, lView: LView, tagName: string|null): boolean {
const schemas = tView.schemas; const schemas = tView.schemas;
if (schemas !== null) { if (schemas !== null) {
@ -1099,8 +1099,8 @@ export function instantiateRootComponent<T>(tView: TView, lView: LView, def: Com
* Resolve the matched directives on a node. * Resolve the matched directives on a node.
*/ */
export function resolveDirectives( export function resolveDirectives(
tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode, tView: TView, lView: LView, tNode: TElementNode|TContainerNode|TElementContainerNode,
localRefs: string[] | null): boolean { localRefs: string[]|null): boolean {
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
// tsickle. // tsickle.
ngDevMode && assertFirstCreatePass(tView); ngDevMode && assertFirstCreatePass(tView);
@ -1108,7 +1108,7 @@ export function resolveDirectives(
let hasDirectives = false; let hasDirectives = false;
if (getBindingsEnabled()) { if (getBindingsEnabled()) {
const directiveDefs: DirectiveDef<any>[]|null = findDirectiveDefMatches(tView, lView, tNode); const directiveDefs: DirectiveDef<any>[]|null = findDirectiveDefMatches(tView, lView, tNode);
const exportsMap: ({[key: string]: number} | null) = localRefs === null ? null : {'': -1}; const exportsMap: ({[key: string]: number}|null) = localRefs === null ? null : {'': -1};
if (directiveDefs !== null) { if (directiveDefs !== null) {
let totalDirectiveHostVars = 0; let totalDirectiveHostVars = 0;
@ -1135,7 +1135,7 @@ export function resolveDirectives(
baseResolveDirective(tView, lView, def); baseResolveDirective(tView, lView, def);
saveNameToExportMap(tView.data !.length - 1, def, exportsMap); saveNameToExportMap(tView.data!.length - 1, def, exportsMap);
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery; if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0) if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
@ -1152,8 +1152,8 @@ export function resolveDirectives(
} }
if (!preOrderCheckHooksFound && (def.onChanges || def.doCheck)) { if (!preOrderCheckHooksFound && (def.onChanges || def.doCheck)) {
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [ (tView.preOrderCheckHooks || (tView.preOrderCheckHooks = []))
])).push(tNode.index - HEADER_OFFSET); .push(tNode.index - HEADER_OFFSET);
preOrderCheckHooksFound = true; preOrderCheckHooksFound = true;
} }
@ -1178,9 +1178,9 @@ export function resolveDirectives(
* @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add. * @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add.
*/ */
export function addHostBindingsToExpandoInstructions( export function addHostBindingsToExpandoInstructions(
tView: TView, def: ComponentDef<any>| DirectiveDef<any>): void { tView: TView, def: ComponentDef<any>|DirectiveDef<any>): void {
ngDevMode && assertFirstCreatePass(tView); ngDevMode && assertFirstCreatePass(tView);
const expando = tView.expandoInstructions !; const expando = tView.expandoInstructions!;
// TODO(misko): PERF we are adding `hostBindings` even if there is nothing to add! This is // TODO(misko): PERF we are adding `hostBindings` even if there is nothing to add! This is
// suboptimal for performance. `def.hostBindings` may be null, // suboptimal for performance. `def.hostBindings` may be null,
// but we still need to push null to the array as a placeholder // but we still need to push null to the array as a placeholder
@ -1244,7 +1244,7 @@ function instantiateAllDirectives(
attachPatchData(directive, lView); attachPatchData(directive, lView);
if (initialInputs !== null) { if (initialInputs !== null) {
setInputsFromAttrs(lView, i - start, directive, def, tNode, initialInputs !); setInputsFromAttrs(lView, i - start, directive, def, tNode, initialInputs!);
} }
if (isComponent) { if (isComponent) {
@ -1257,7 +1257,7 @@ function instantiateAllDirectives(
function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) { function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) {
const start = tNode.directiveStart; const start = tNode.directiveStart;
const end = tNode.directiveEnd; const end = tNode.directiveEnd;
const expando = tView.expandoInstructions !; const expando = tView.expandoInstructions!;
const firstCreatePass = tView.firstCreatePass; const firstCreatePass = tView.firstCreatePass;
const elementIndex = tNode.index - HEADER_OFFSET; const elementIndex = tNode.index - HEADER_OFFSET;
try { try {
@ -1284,7 +1284,7 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode)
*/ */
export function invokeHostBindingsInCreationMode(def: DirectiveDef<any>, directive: any) { export function invokeHostBindingsInCreationMode(def: DirectiveDef<any>, directive: any) {
if (def.hostBindings !== null) { if (def.hostBindings !== null) {
def.hostBindings !(RenderFlags.Create, directive); def.hostBindings!(RenderFlags.Create, directive);
} }
} }
@ -1296,9 +1296,10 @@ export function invokeHostBindingsInCreationMode(def: DirectiveDef<any>, directi
*/ */
export function generateExpandoInstructionBlock( export function generateExpandoInstructionBlock(
tView: TView, tNode: TNode, directiveCount: number): void { tView: TView, tNode: TNode, directiveCount: number): void {
ngDevMode && assertEqual( ngDevMode &&
tView.firstCreatePass, true, assertEqual(
'Expando block should only be generated on first create pass.'); tView.firstCreatePass, true,
'Expando block should only be generated on first create pass.');
// Important: In JS `-x` and `0-x` is not the same! If `x===0` then `-x` will produce `-0` which // Important: In JS `-x` and `0-x` is not the same! If `x===0` then `-x` will produce `-0` which
// requires non standard math arithmetic and it can prevent VM optimizations. // requires non standard math arithmetic and it can prevent VM optimizations.
@ -1306,26 +1307,27 @@ export function generateExpandoInstructionBlock(
const elementIndex = HEADER_OFFSET - tNode.index; const elementIndex = HEADER_OFFSET - tNode.index;
const providerStartIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask; const providerStartIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const providerCount = tView.data.length - providerStartIndex; const providerCount = tView.data.length - providerStartIndex;
(tView.expandoInstructions || (tView.expandoInstructions = [ (tView.expandoInstructions || (tView.expandoInstructions = []))
])).push(elementIndex, providerCount, directiveCount); .push(elementIndex, providerCount, directiveCount);
} }
/** /**
* Matches the current node against all available selectors. * Matches the current node against all available selectors.
* If a component is matched (at most one), it is returned in first position in the array. * If a component is matched (at most one), it is returned in first position in the array.
*/ */
function findDirectiveDefMatches( function findDirectiveDefMatches(
tView: TView, viewData: LView, tView: TView, viewData: LView,
tNode: TElementNode | TContainerNode | TElementContainerNode): DirectiveDef<any>[]|null { tNode: TElementNode|TContainerNode|TElementContainerNode): DirectiveDef<any>[]|null {
ngDevMode && assertFirstCreatePass(tView); ngDevMode && assertFirstCreatePass(tView);
ngDevMode && assertNodeOfPossibleTypes( ngDevMode &&
tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container); assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container);
const registry = tView.directiveRegistry; const registry = tView.directiveRegistry;
let matches: any[]|null = null; let matches: any[]|null = null;
if (registry) { if (registry) {
for (let i = 0; i < registry.length; i++) { for (let i = 0; i < registry.length; i++) {
const def = registry[i] as ComponentDef<any>| DirectiveDef<any>; const def = registry[i] as ComponentDef<any>| DirectiveDef<any>;
if (isNodeMatchingSelectorList(tNode, def.selectors !, /* isProjectionMode */ false)) { if (isNodeMatchingSelectorList(tNode, def.selectors!, /* isProjectionMode */ false)) {
matches || (matches = ngDevMode ? new MatchesArray() : []); matches || (matches = ngDevMode ? new MatchesArray() : []);
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, viewData), tView, def.type); diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, viewData), tView, def.type);
@ -1347,21 +1349,20 @@ function findDirectiveDefMatches(
* Marks a given TNode as a component's host. This consists of: * Marks a given TNode as a component's host. This consists of:
* - setting appropriate TNode flags; * - setting appropriate TNode flags;
* - storing index of component's host element so it will be queued for view refresh during CD. * - storing index of component's host element so it will be queued for view refresh during CD.
*/ */
export function markAsComponentHost(tView: TView, hostTNode: TNode): void { export function markAsComponentHost(tView: TView, hostTNode: TNode): void {
ngDevMode && assertFirstCreatePass(tView); ngDevMode && assertFirstCreatePass(tView);
hostTNode.flags |= TNodeFlags.isComponentHost; hostTNode.flags |= TNodeFlags.isComponentHost;
(tView.components || (tView.components = ngDevMode ? new TViewComponents() : [ (tView.components || (tView.components = ngDevMode ? new TViewComponents() : []))
])).push(hostTNode.index); .push(hostTNode.index);
} }
/** Caches local names and their matching directive indices for query and template lookups. */ /** Caches local names and their matching directive indices for query and template lookups. */
function cacheMatchingLocalNames( function cacheMatchingLocalNames(
tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void { tNode: TNode, localRefs: string[]|null, exportsMap: {[key: string]: number}): void {
if (localRefs) { if (localRefs) {
const localNames: (string | number)[] = tNode.localNames = const localNames: (string|number)[] = tNode.localNames = ngDevMode ? new TNodeLocalNames() : [];
ngDevMode ? new TNodeLocalNames() : [];
// Local names must be stored in tNode in the same order that localRefs are defined // Local names must be stored in tNode in the same order that localRefs are defined
// in the template to ensure the data is loaded in the same slots as their refs // in the template to ensure the data is loaded in the same slots as their refs
@ -1375,12 +1376,12 @@ function cacheMatchingLocalNames(
} }
/** /**
* Builds up an export map as directives are created, so local refs can be quickly mapped * Builds up an export map as directives are created, so local refs can be quickly mapped
* to their directive instances. * to their directive instances.
*/ */
function saveNameToExportMap( function saveNameToExportMap(
index: number, def: DirectiveDef<any>| ComponentDef<any>, index: number, def: DirectiveDef<any>|ComponentDef<any>,
exportsMap: {[key: string]: number} | null) { exportsMap: {[key: string]: number}|null) {
if (exportsMap) { if (exportsMap) {
if (def.exportAs) { if (def.exportAs) {
for (let i = 0; i < def.exportAs.length; i++) { for (let i = 0; i < def.exportAs.length; i++) {
@ -1397,9 +1398,10 @@ function saveNameToExportMap(
* @param index the initial index * @param index the initial index
*/ */
export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) { export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) {
ngDevMode && assertNotEqual( ngDevMode &&
numberOfDirectives, tNode.directiveEnd - tNode.directiveStart, assertNotEqual(
'Reached the max number of directives'); numberOfDirectives, tNode.directiveEnd - tNode.directiveStart,
'Reached the max number of directives');
tNode.flags |= TNodeFlags.isDirectiveHost; tNode.flags |= TNodeFlags.isDirectiveHost;
// When the first directive is created on a node, save the index // When the first directive is created on a node, save the index
tNode.directiveStart = index; tNode.directiveStart = index;
@ -1410,7 +1412,7 @@ export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives:
function baseResolveDirective<T>(tView: TView, viewData: LView, def: DirectiveDef<T>) { function baseResolveDirective<T>(tView: TView, viewData: LView, def: DirectiveDef<T>) {
tView.data.push(def); tView.data.push(def);
const directiveFactory = const directiveFactory =
def.factory || ((def as{factory: Function}).factory = getFactoryDef(def.type, true)); def.factory || ((def as {factory: Function}).factory = getFactoryDef(def.type, true));
const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null); const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), null);
tView.blueprint.push(nodeInjectorFactory); tView.blueprint.push(nodeInjectorFactory);
viewData.push(nodeInjectorFactory); viewData.push(nodeInjectorFactory);
@ -1435,8 +1437,8 @@ function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: Compon
} }
export function elementAttributeInternal( export function elementAttributeInternal(
tNode: TNode, lView: LView, name: string, value: any, sanitizer: SanitizerFn | null | undefined, tNode: TNode, lView: LView, name: string, value: any, sanitizer: SanitizerFn|null|undefined,
namespace: string | null | undefined) { namespace: string|null|undefined) {
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
ngDevMode && validateAgainstEventAttributes(name); ngDevMode && validateAgainstEventAttributes(name);
const element = getNativeByTNode(tNode, lView) as RElement; const element = getNativeByTNode(tNode, lView) as RElement;
@ -1472,7 +1474,7 @@ export function elementAttributeInternal(
function setInputsFromAttrs<T>( function setInputsFromAttrs<T>(
lView: LView, directiveIndex: number, instance: T, def: DirectiveDef<T>, tNode: TNode, lView: LView, directiveIndex: number, instance: T, def: DirectiveDef<T>, tNode: TNode,
initialInputData: InitialInputData): void { initialInputData: InitialInputData): void {
const initialInputs: InitialInputs|null = initialInputData ![directiveIndex]; const initialInputs: InitialInputs|null = initialInputData![directiveIndex];
if (initialInputs !== null) { if (initialInputs !== null) {
const setInput = def.setInput; const setInput = def.setInput;
for (let i = 0; i < initialInputs.length;) { for (let i = 0; i < initialInputs.length;) {
@ -1480,7 +1482,7 @@ function setInputsFromAttrs<T>(
const privateName = initialInputs[i++]; const privateName = initialInputs[i++];
const value = initialInputs[i++]; const value = initialInputs[i++];
if (setInput !== null) { if (setInput !== null) {
def.setInput !(instance, value, publicName, privateName); def.setInput!(instance, value, publicName, privateName);
} else { } else {
(instance as any)[privateName] = value; (instance as any)[privateName] = value;
} }
@ -1554,7 +1556,7 @@ const LContainerArray: any = ((typeof ngDevMode === 'undefined' || ngDevMode) &&
* @returns LContainer * @returns LContainer
*/ */
export function createLContainer( export function createLContainer(
hostNative: RElement | RComment | LView, currentView: LView, native: RComment, hostNative: RElement|RComment|LView, currentView: LView, native: RComment,
tNode: TNode): LContainer { tNode: TNode): LContainer {
ngDevMode && assertLView(currentView); ngDevMode && assertLView(currentView);
ngDevMode && !isProceduralRenderer(currentView[RENDERER]) && assertDomNode(native); ngDevMode && !isProceduralRenderer(currentView[RENDERER]) && assertDomNode(native);
@ -1569,7 +1571,7 @@ export function createLContainer(
tNode, // t_host tNode, // t_host
native, // native, native, // native,
null, // view refs null, // view refs
); );
ngDevMode && attachLContainerDebug(lContainer); ngDevMode && attachLContainerDebug(lContainer);
return lContainer; return lContainer;
} }
@ -1594,14 +1596,14 @@ function refreshDynamicEmbeddedViews(lView: LView) {
ngDevMode && assertDefined(embeddedTView, 'TView must be allocated'); ngDevMode && assertDefined(embeddedTView, 'TView must be allocated');
if (viewAttachedToChangeDetector(embeddedLView)) { if (viewAttachedToChangeDetector(embeddedLView)) {
refreshView( refreshView(
embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT] !); embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!);
} }
} }
if ((activeIndexFlag & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) !== 0) { if ((activeIndexFlag & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) !== 0) {
// We should only CD moved views if the component where they were inserted does not match // We should only CD moved views if the component where they were inserted does not match
// the component where they were declared and insertion is on-push. Moved views also // the component where they were declared and insertion is on-push. Moved views also
// contains intra component moves, or check-always which need to be skipped. // contains intra component moves, or check-always which need to be skipped.
refreshTransplantedViews(viewOrContainer, lView[DECLARATION_COMPONENT_VIEW] !); refreshTransplantedViews(viewOrContainer, lView[DECLARATION_COMPONENT_VIEW]!);
} }
} }
viewOrContainer = viewOrContainer[NEXT]; viewOrContainer = viewOrContainer[NEXT];
@ -1619,13 +1621,13 @@ function refreshDynamicEmbeddedViews(lView: LView) {
* @param declaredComponentLView The `lContainer` parent component `LView`. * @param declaredComponentLView The `lContainer` parent component `LView`.
*/ */
function refreshTransplantedViews(lContainer: LContainer, declaredComponentLView: LView) { function refreshTransplantedViews(lContainer: LContainer, declaredComponentLView: LView) {
const movedViews = lContainer[MOVED_VIEWS] !; const movedViews = lContainer[MOVED_VIEWS]!;
ngDevMode && assertDefined(movedViews, 'Transplanted View flags set but missing MOVED_VIEWS'); ngDevMode && assertDefined(movedViews, 'Transplanted View flags set but missing MOVED_VIEWS');
for (let i = 0; i < movedViews.length; i++) { for (let i = 0; i < movedViews.length; i++) {
const movedLView = movedViews[i] !; const movedLView = movedViews[i]!;
const insertionLContainer = movedLView[PARENT] as LContainer; const insertionLContainer = movedLView[PARENT] as LContainer;
ngDevMode && assertLContainer(insertionLContainer); ngDevMode && assertLContainer(insertionLContainer);
const insertedComponentLView = insertionLContainer[PARENT][DECLARATION_COMPONENT_VIEW] !; const insertedComponentLView = insertionLContainer[PARENT][DECLARATION_COMPONENT_VIEW]!;
ngDevMode && assertDefined(insertedComponentLView, 'Missing LView'); ngDevMode && assertDefined(insertedComponentLView, 'Missing LView');
// Check if we have a transplanted view by compering declaration and insertion location. // Check if we have a transplanted view by compering declaration and insertion location.
if (insertedComponentLView !== declaredComponentLView) { if (insertedComponentLView !== declaredComponentLView) {
@ -1643,7 +1645,7 @@ function refreshTransplantedViews(lContainer: LContainer, declaredComponentLView
// point. // point.
const movedTView = movedLView[TVIEW]; const movedTView = movedLView[TVIEW];
ngDevMode && assertDefined(movedTView, 'TView must be allocated'); ngDevMode && assertDefined(movedTView, 'TView must be allocated');
refreshView(movedTView, movedLView, movedTView.template, movedLView[CONTEXT] !); refreshView(movedTView, movedLView, movedTView.template, movedLView[CONTEXT]!);
} }
} }
} }
@ -1725,7 +1727,7 @@ export function addToViewTree<T extends LView|LContainer>(lView: LView, lViewOrL
// of order, the change detection will run out of order, as the act of retrieving the the // of order, the change detection will run out of order, as the act of retrieving the the
// LContainer from the RNode is what adds it to the queue. // LContainer from the RNode is what adds it to the queue.
if (lView[CHILD_HEAD]) { if (lView[CHILD_HEAD]) {
lView[CHILD_TAIL] ![NEXT] = lViewOrLContainer; lView[CHILD_TAIL]![NEXT] = lViewOrLContainer;
} else { } else {
lView[CHILD_HEAD] = lViewOrLContainer; lView[CHILD_HEAD] = lViewOrLContainer;
} }
@ -1758,7 +1760,7 @@ export function markViewDirty(lView: LView): LView|null {
return lView; return lView;
} }
// continue otherwise // continue otherwise
lView = parent !; lView = parent!;
} }
return null; return null;
} }
@ -1797,7 +1799,7 @@ export function scheduleTick(rootContext: RootContext, flags: RootContextFlags)
} }
rootContext.clean = _CLEAN_PROMISE; rootContext.clean = _CLEAN_PROMISE;
res !(null); res!(null);
}); });
} }
} }
@ -1805,7 +1807,7 @@ export function scheduleTick(rootContext: RootContext, flags: RootContextFlags)
export function tickRootContext(rootContext: RootContext) { export function tickRootContext(rootContext: RootContext) {
for (let i = 0; i < rootContext.components.length; i++) { for (let i = 0; i < rootContext.components.length; i++) {
const rootComponent = rootContext.components[i]; const rootComponent = rootContext.components[i];
const lView = readPatchedLView(rootComponent) !; const lView = readPatchedLView(rootComponent)!;
const tView = lView[TVIEW]; const tView = lView[TVIEW];
renderComponentOrTemplate(tView, lView, tView.template, rootComponent); renderComponentOrTemplate(tView, lView, tView.template, rootComponent);
} }
@ -1929,7 +1931,7 @@ function getTViewCleanup(tView: TView): any[] {
* instead of the current renderer (see the componentSyntheticHost* instructions). * instead of the current renderer (see the componentSyntheticHost* instructions).
*/ */
export function loadComponentRenderer(tNode: TNode, lView: LView): Renderer3 { export function loadComponentRenderer(tNode: TNode, lView: LView): Renderer3 {
const componentLView = unwrapLView(lView[tNode.index]) !; const componentLView = unwrapLView(lView[tNode.index])!;
return componentLView[RENDERER]; return componentLView[RENDERER];
} }
@ -1958,7 +1960,7 @@ export function setInputsForProperty(
ngDevMode && assertDataInRange(lView, index); ngDevMode && assertDataInRange(lView, index);
const def = tView.data[index] as DirectiveDef<any>; const def = tView.data[index] as DirectiveDef<any>;
if (def.setInput !== null) { if (def.setInput !== null) {
def.setInput !(instance, value, publicName, privateName); def.setInput!(instance, value, publicName, privateName);
} else { } else {
instance[privateName] = value; instance[privateName] = value;
} }

View File

@ -365,8 +365,10 @@ export const enum InitPhaseState {
/** More flags associated with an LView (saved in LView[PREORDER_HOOK_FLAGS]) */ /** More flags associated with an LView (saved in LView[PREORDER_HOOK_FLAGS]) */
export const enum PreOrderHookFlags { export const enum PreOrderHookFlags {
/** The index of the next pre-order hook to be called in the hooks array, on the first 16 /**
bits */ The index of the next pre-order hook to be called in the hooks array, on the first 16
bits
*/
IndexOfTheNextPreOrderHookMaskMask = 0b01111111111111111, IndexOfTheNextPreOrderHookMaskMask = 0b01111111111111111,
/** /**
@ -617,7 +619,7 @@ export interface TView {
* Even indices: Directive index * Even indices: Directive index
* Odd indices: Hook function * Odd indices: Hook function
*/ */
destroyHooks: HookData|null; destroyHooks: DestroyHookData|null;
/** /**
* When a view is destroyed, listeners need to be released and outputs need to be * When a view is destroyed, listeners need to be released and outputs need to be
@ -684,7 +686,11 @@ export interface TView {
consts: TConstants|null; consts: TConstants|null;
} }
export const enum RootContextFlags {Empty = 0b00, DetectChanges = 0b01, FlushPlayers = 0b10} export const enum RootContextFlags {
Empty = 0b00,
DetectChanges = 0b01,
FlushPlayers = 0b10
}
/** /**
@ -722,6 +728,15 @@ export interface RootContext {
flags: RootContextFlags; flags: RootContextFlags;
} }
/** Single hook callback function. */
export type HookFn = () => void;
/**
* Information necessary to call a hook. E.g. the callback that
* needs to invoked and the index at which to find its context.
*/
export type HookEntry = number|HookFn;
/** /**
* Array of hooks that should be executed for a view and their directive indices. * Array of hooks that should be executed for a view and their directive indices.
* *
@ -734,7 +749,27 @@ export interface RootContext {
* Special cases: * Special cases:
* - a negative directive index flags an init hook (ngOnInit, ngAfterContentInit, ngAfterViewInit) * - a negative directive index flags an init hook (ngOnInit, ngAfterContentInit, ngAfterViewInit)
*/ */
export type HookData = (number | (() => void))[]; export type HookData = HookEntry[];
/**
* Array of destroy hooks that should be executed for a view and their directive indices.
*
* The array is set up as a series of number/function or number/(number|function)[]:
* - Even indices represent the context with which hooks should be called.
* - Odd indices are the hook functions themselves. If a value at an odd index is an array,
* it represents the destroy hooks of a `multi` provider where:
* - Even indices represent the index of the provider for which we've registered a destroy hook,
* inside of the `multi` provider array.
* - Odd indices are the destroy hook functions.
* For example:
* LView: `[0, 1, 2, AService, 4, [BService, CService, DService]]`
* destroyHooks: `[3, AService.ngOnDestroy, 5, [0, BService.ngOnDestroy, 2, DService.ngOnDestroy]]`
*
* In the example above `AService` is a type provider with an `ngOnDestroy`, whereas `BService`,
* `CService` and `DService` are part of a `multi` provider where only `BService` and `DService`
* have an `ngOnDestroy` hook.
*/
export type DestroyHookData = (HookEntry|HookData)[];
/** /**
* Static data that corresponds to the instance-specific data array on an LView. * Static data that corresponds to the instance-specific data array on an LView.
@ -764,8 +799,8 @@ export type HookData = (number | (() => void))[];
* Injector bloom filters are also stored here. * Injector bloom filters are also stored here.
*/ */
export type TData = export type TData =
(TNode | PipeDef<any>| DirectiveDef<any>| ComponentDef<any>| number | TStylingRange | (TNode|PipeDef<any>|DirectiveDef<any>|ComponentDef<any>|number|TStylingRange|TStylingKey|
TStylingKey | Type<any>| InjectionToken<any>| TI18n | I18nUpdateOpCodes | null | string)[]; Type<any>|InjectionToken<any>|TI18n|I18nUpdateOpCodes|null|string)[];
// Note: This hack is necessary so we don't erroneously get a circular dependency // Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types. // failure based on types.

View File

@ -10,6 +10,7 @@ import {Renderer2} from '../core';
import {ViewEncapsulation} from '../metadata/view'; import {ViewEncapsulation} from '../metadata/view';
import {addToArray, removeFromArray} from '../util/array_utils'; import {addToArray, removeFromArray} from '../util/array_utils';
import {assertDefined, assertDomNode, assertEqual, assertString} from '../util/assert'; import {assertDefined, assertDomNode, assertEqual, assertString} from '../util/assert';
import {assertLContainer, assertLView, assertTNodeForLView} from './assert'; import {assertLContainer, assertLView, assertTNodeForLView} from './assert';
import {attachPatchData} from './context_discovery'; import {attachPatchData} from './context_discovery';
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
@ -17,9 +18,9 @@ import {ComponentDef} from './interfaces/definition';
import {NodeInjectorFactory} from './interfaces/injector'; import {NodeInjectorFactory} from './interfaces/injector';
import {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {isProceduralRenderer, ProceduralRenderer3, RElement, Renderer3, RNode, RText, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {isLContainer, isLView} from './interfaces/type_checks'; import {isLContainer, isLView} from './interfaces/type_checks';
import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, TView, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {getLViewParent} from './util/view_traversal_utils'; import {getLViewParent} from './util/view_traversal_utils';
import {getNativeByTNode, unwrapRNode} from './util/view_utils'; import {getNativeByTNode, unwrapRNode} from './util/view_utils';
@ -74,8 +75,8 @@ const enum WalkTNodeTreeAction {
* being passed as an argument. * being passed as an argument.
*/ */
function applyToElementOrContainer( function applyToElementOrContainer(
action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null, action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement|null,
lNodeToHandle: RNode | LContainer | LView, beforeNode?: RNode | null) { lNodeToHandle: RNode|LContainer|LView, beforeNode?: RNode|null) {
// If this slot was allocated for a text node dynamically created by i18n, the text node itself // If this slot was allocated for a text node dynamically created by i18n, the text node itself
// won't be created until i18nApply() in the update block, so this node should be skipped. // won't be created until i18nApply() in the update block, so this node should be skipped.
// For more info, see "ICU expressions should work inside an ngTemplateOutlet inside an ngFor" // For more info, see "ICU expressions should work inside an ngTemplateOutlet inside an ngFor"
@ -91,7 +92,7 @@ function applyToElementOrContainer(
} else if (isLView(lNodeToHandle)) { } else if (isLView(lNodeToHandle)) {
isComponent = true; isComponent = true;
ngDevMode && assertDefined(lNodeToHandle[HOST], 'HOST must be defined for a component LView'); ngDevMode && assertDefined(lNodeToHandle[HOST], 'HOST must be defined for a component LView');
lNodeToHandle = lNodeToHandle[HOST] !; lNodeToHandle = lNodeToHandle[HOST]!;
} }
const rNode: RNode = unwrapRNode(lNodeToHandle); const rNode: RNode = unwrapRNode(lNodeToHandle);
ngDevMode && !isProceduralRenderer(renderer) && assertDomNode(rNode); ngDevMode && !isProceduralRenderer(renderer) && assertDomNode(rNode);
@ -108,7 +109,7 @@ function applyToElementOrContainer(
nativeRemoveNode(renderer, rNode, isComponent); nativeRemoveNode(renderer, rNode, isComponent);
} else if (action === WalkTNodeTreeAction.Destroy) { } else if (action === WalkTNodeTreeAction.Destroy) {
ngDevMode && ngDevMode.rendererDestroyNode++; ngDevMode && ngDevMode.rendererDestroyNode++;
(renderer as ProceduralRenderer3).destroyNode !(rNode); (renderer as ProceduralRenderer3).destroyNode!(rNode);
} }
if (lContainer != null) { if (lContainer != null) {
applyContainer(renderer, action, lContainer, parent, beforeNode); applyContainer(renderer, action, lContainer, parent, beforeNode);
@ -136,11 +137,11 @@ export function createTextNode(value: string, renderer: Renderer3): RText {
* @param beforeNode The node before which elements should be added, if insert mode * @param beforeNode The node before which elements should be added, if insert mode
*/ */
export function addRemoveViewFromContainer( export function addRemoveViewFromContainer(
tView: TView, lView: LView, insertMode: true, beforeNode: RNode | null): void; tView: TView, lView: LView, insertMode: true, beforeNode: RNode|null): void;
export function addRemoveViewFromContainer( export function addRemoveViewFromContainer(
tView: TView, lView: LView, insertMode: false, beforeNode: null): void; tView: TView, lView: LView, insertMode: false, beforeNode: null): void;
export function addRemoveViewFromContainer( export function addRemoveViewFromContainer(
tView: TView, lView: LView, insertMode: boolean, beforeNode: RNode | null): void { tView: TView, lView: LView, insertMode: boolean, beforeNode: RNode|null): void {
const renderParent = getContainerRenderParent(tView.node as TViewNode, lView); const renderParent = getContainerRenderParent(tView.node as TViewNode, lView);
ngDevMode && assertNodeType(tView.node as TNode, TNodeType.View); ngDevMode && assertNodeType(tView.node as TNode, TNodeType.View);
if (renderParent) { if (renderParent) {
@ -196,13 +197,13 @@ export function destroyViewTree(rootView: LView): void {
if (!next) { if (!next) {
// Only clean up view when moving to the side or up, as destroy hooks // Only clean up view when moving to the side or up, as destroy hooks
// should be called in order from the bottom up. // should be called in order from the bottom up.
while (lViewOrLContainer && !lViewOrLContainer ![NEXT] && lViewOrLContainer !== rootView) { while (lViewOrLContainer && !lViewOrLContainer![NEXT] && lViewOrLContainer !== rootView) {
isLView(lViewOrLContainer) && cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer); isLView(lViewOrLContainer) && cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer);
lViewOrLContainer = getParentState(lViewOrLContainer, rootView); lViewOrLContainer = getParentState(lViewOrLContainer, rootView);
} }
if (lViewOrLContainer === null) lViewOrLContainer = rootView; if (lViewOrLContainer === null) lViewOrLContainer = rootView;
isLView(lViewOrLContainer) && cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer); isLView(lViewOrLContainer) && cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer);
next = lViewOrLContainer && lViewOrLContainer ![NEXT]; next = lViewOrLContainer && lViewOrLContainer![NEXT];
} }
lViewOrLContainer = next; lViewOrLContainer = next;
} }
@ -267,7 +268,7 @@ function trackMovedView(declarationContainer: LContainer, lView: LView) {
const movedViews = declarationContainer[MOVED_VIEWS]; const movedViews = declarationContainer[MOVED_VIEWS];
const insertedLContainer = lView[PARENT] as LContainer; const insertedLContainer = lView[PARENT] as LContainer;
ngDevMode && assertLContainer(insertedLContainer); ngDevMode && assertLContainer(insertedLContainer);
const insertedComponentLView = insertedLContainer[PARENT] ![DECLARATION_COMPONENT_VIEW]; const insertedComponentLView = insertedLContainer[PARENT]![DECLARATION_COMPONENT_VIEW];
ngDevMode && assertDefined(insertedComponentLView, 'Missing insertedComponentLView'); ngDevMode && assertDefined(insertedComponentLView, 'Missing insertedComponentLView');
const insertedComponentIsOnPush = const insertedComponentIsOnPush =
(insertedComponentLView[FLAGS] & LViewFlags.CheckAlways) !== LViewFlags.CheckAlways; (insertedComponentLView[FLAGS] & LViewFlags.CheckAlways) !== LViewFlags.CheckAlways;
@ -291,10 +292,11 @@ function trackMovedView(declarationContainer: LContainer, lView: LView) {
function detachMovedView(declarationContainer: LContainer, lView: LView) { function detachMovedView(declarationContainer: LContainer, lView: LView) {
ngDevMode && assertLContainer(declarationContainer); ngDevMode && assertLContainer(declarationContainer);
ngDevMode && assertDefined( ngDevMode &&
declarationContainer[MOVED_VIEWS], assertDefined(
'A projected view should belong to a non-empty projected views collection'); declarationContainer[MOVED_VIEWS],
const movedViews = declarationContainer[MOVED_VIEWS] !; 'A projected view should belong to a non-empty projected views collection');
const movedViews = declarationContainer[MOVED_VIEWS]!;
const declaredViewIndex = movedViews.indexOf(lView); const declaredViewIndex = movedViews.indexOf(lView);
movedViews.splice(declaredViewIndex, 1); movedViews.splice(declaredViewIndex, 1);
} }
@ -383,7 +385,7 @@ export function destroyLView(tView: TView, lView: LView) {
* @param rootView The rootView, so we don't propagate too far up the view tree * @param rootView The rootView, so we don't propagate too far up the view tree
* @returns The correct parent LViewOrLContainer * @returns The correct parent LViewOrLContainer
*/ */
export function getParentState(lViewOrLContainer: LView | LContainer, rootView: LView): LView| export function getParentState(lViewOrLContainer: LView|LContainer, rootView: LView): LView|
LContainer|null { LContainer|null {
let tNode; let tNode;
if (isLView(lViewOrLContainer) && (tNode = lViewOrLContainer[T_HOST]) && if (isLView(lViewOrLContainer) && (tNode = lViewOrLContainer[T_HOST]) &&
@ -449,7 +451,7 @@ function cleanUpView(tView: TView, lView: LView): void {
function removeListeners(tView: TView, lView: LView): void { function removeListeners(tView: TView, lView: LView): void {
const tCleanup = tView.cleanup; const tCleanup = tView.cleanup;
if (tCleanup !== null) { if (tCleanup !== null) {
const lCleanup = lView[CLEANUP] !; const lCleanup = lView[CLEANUP]!;
for (let i = 0; i < tCleanup.length - 1; i += 2) { for (let i = 0; i < tCleanup.length - 1; i += 2) {
if (typeof tCleanup[i] === 'string') { if (typeof tCleanup[i] === 'string') {
// This is a native DOM listener // This is a native DOM listener
@ -484,7 +486,7 @@ function removeListeners(tView: TView, lView: LView): void {
/** Calls onDestroy hooks for this view */ /** Calls onDestroy hooks for this view */
function executeOnDestroys(tView: TView, lView: LView): void { function executeOnDestroys(tView: TView, lView: LView): void {
let destroyHooks: HookData|null; let destroyHooks: DestroyHookData|null;
if (tView != null && (destroyHooks = tView.destroyHooks) != null) { if (tView != null && (destroyHooks = tView.destroyHooks) != null) {
for (let i = 0; i < destroyHooks.length; i += 2) { for (let i = 0; i < destroyHooks.length; i += 2) {
@ -492,7 +494,15 @@ function executeOnDestroys(tView: TView, lView: LView): void {
// Only call the destroy hook if the context has been requested. // Only call the destroy hook if the context has been requested.
if (!(context instanceof NodeInjectorFactory)) { if (!(context instanceof NodeInjectorFactory)) {
(destroyHooks[i + 1] as() => void).call(context); const toCall = destroyHooks[i + 1] as HookFn | HookData;
if (Array.isArray(toCall)) {
for (let j = 0; j < toCall.length; j += 2) {
(toCall[j + 1] as HookFn).call(context[toCall[j] as number]);
}
} else {
toCall.call(context);
}
} }
} }
} }
@ -514,8 +524,9 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme
// Skip over element and ICU containers as those are represented by a comment node and // Skip over element and ICU containers as those are represented by a comment node and
// can't be used as a render parent. // can't be used as a render parent.
let parentTNode = tNode.parent; let parentTNode = tNode.parent;
while (parentTNode != null && (parentTNode.type === TNodeType.ElementContainer || while (parentTNode != null &&
parentTNode.type === TNodeType.IcuContainer)) { (parentTNode.type === TNodeType.ElementContainer ||
parentTNode.type === TNodeType.IcuContainer)) {
tNode = parentTNode; tNode = parentTNode;
parentTNode = tNode.parent; parentTNode = tNode.parent;
} }
@ -523,7 +534,7 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme
// If the parent tNode is null, then we are inserting across views: either into an embedded view // If the parent tNode is null, then we are inserting across views: either into an embedded view
// or a component view. // or a component view.
if (parentTNode == null) { if (parentTNode == null) {
const hostTNode = currentView[T_HOST] !; const hostTNode = currentView[T_HOST]!;
if (hostTNode.type === TNodeType.View) { if (hostTNode.type === TNodeType.View) {
// We are inserting a root element of an embedded view We might delay insertion of children // We are inserting a root element of an embedded view We might delay insertion of children
// for a given view if it is disconnected. This might happen for 2 main reasons: // for a given view if it is disconnected. This might happen for 2 main reasons:
@ -575,7 +586,7 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme
* actual renderer being used. * actual renderer being used.
*/ */
export function nativeInsertBefore( export function nativeInsertBefore(
renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode | null): void { renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode|null): void {
ngDevMode && ngDevMode.rendererInsertBefore++; ngDevMode && ngDevMode.rendererInsertBefore++;
if (isProceduralRenderer(renderer)) { if (isProceduralRenderer(renderer)) {
renderer.insertBefore(parent, child, beforeNode); renderer.insertBefore(parent, child, beforeNode);
@ -595,7 +606,7 @@ function nativeAppendChild(renderer: Renderer3, parent: RElement, child: RNode):
} }
function nativeAppendOrInsertBefore( function nativeAppendOrInsertBefore(
renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode | null) { renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode|null) {
if (beforeNode !== null) { if (beforeNode !== null) {
nativeInsertBefore(renderer, parent, child, beforeNode); nativeInsertBefore(renderer, parent, child, beforeNode);
} else { } else {
@ -659,11 +670,11 @@ function getNativeAnchorNode(parentTNode: TNode, lView: LView): RNode|null {
* @returns Whether or not the child was appended * @returns Whether or not the child was appended
*/ */
export function appendChild( export function appendChild(
tView: TView, lView: LView, childEl: RNode | RNode[], childTNode: TNode): void { tView: TView, lView: LView, childEl: RNode|RNode[], childTNode: TNode): void {
const renderParent = getRenderParent(tView, childTNode, lView); const renderParent = getRenderParent(tView, childTNode, lView);
if (renderParent != null) { if (renderParent != null) {
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
const parentTNode: TNode = childTNode.parent || lView[T_HOST] !; const parentTNode: TNode = childTNode.parent || lView[T_HOST]!;
const anchorNode = getNativeAnchorNode(parentTNode, lView); const anchorNode = getNativeAnchorNode(parentTNode, lView);
if (Array.isArray(childEl)) { if (Array.isArray(childEl)) {
for (let i = 0; i < childEl.length; i++) { for (let i = 0; i < childEl.length; i++) {
@ -680,11 +691,12 @@ export function appendChild(
* *
* Native nodes are returned in the order in which those appear in the native tree (DOM). * Native nodes are returned in the order in which those appear in the native tree (DOM).
*/ */
function getFirstNativeNode(lView: LView, tNode: TNode | null): RNode|null { function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null {
if (tNode !== null) { if (tNode !== null) {
ngDevMode && assertNodeOfPossibleTypes( ngDevMode &&
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer, assertNodeOfPossibleTypes(
TNodeType.IcuContainer, TNodeType.Projection); tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer,
TNodeType.IcuContainer, TNodeType.Projection);
const tNodeType = tNode.type; const tNodeType = tNode.type;
if (tNodeType === TNodeType.Element) { if (tNodeType === TNodeType.Element) {
@ -708,10 +720,10 @@ function getFirstNativeNode(lView: LView, tNode: TNode | null): RNode|null {
const componentHost = componentView[T_HOST] as TElementNode; const componentHost = componentView[T_HOST] as TElementNode;
const parentView = getLViewParent(componentView); const parentView = getLViewParent(componentView);
const firstProjectedTNode: TNode|null = const firstProjectedTNode: TNode|null =
(componentHost.projection as(TNode | null)[])[tNode.projection as number]; (componentHost.projection as (TNode | null)[])[tNode.projection as number];
if (firstProjectedTNode != null) { if (firstProjectedTNode != null) {
return getFirstNativeNode(parentView !, firstProjectedTNode); return getFirstNativeNode(parentView!, firstProjectedTNode);
} else { } else {
return getFirstNativeNode(lView, tNode.next); return getFirstNativeNode(lView, tNode.next);
} }
@ -757,13 +769,14 @@ export function nativeRemoveNode(renderer: Renderer3, rNode: RNode, isHostElemen
* nodes on the LView or projection boundary. * nodes on the LView or projection boundary.
*/ */
function applyNodes( function applyNodes(
renderer: Renderer3, action: WalkTNodeTreeAction, tNode: TNode | null, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction, tNode: TNode|null, lView: LView,
renderParent: RElement | null, beforeNode: RNode | null, isProjection: boolean) { renderParent: RElement|null, beforeNode: RNode|null, isProjection: boolean) {
while (tNode != null) { while (tNode != null) {
ngDevMode && assertTNodeForLView(tNode, lView); ngDevMode && assertTNodeForLView(tNode, lView);
ngDevMode && assertNodeOfPossibleTypes( ngDevMode &&
tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer, assertNodeOfPossibleTypes(
TNodeType.Projection, TNodeType.Projection, TNodeType.IcuContainer); tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer,
TNodeType.Projection, TNodeType.Projection, TNodeType.IcuContainer);
const rawSlotValue = lView[tNode.index]; const rawSlotValue = lView[tNode.index];
const tNodeType = tNode.type; const tNodeType = tNode.type;
if (isProjection) { if (isProjection) {
@ -814,9 +827,9 @@ function applyNodes(
*/ */
function applyView( function applyView(
tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction, tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction,
renderParent: RElement | null, beforeNode: RNode | null) { renderParent: RElement|null, beforeNode: RNode|null) {
ngDevMode && assertNodeType(tView.node !, TNodeType.View); ngDevMode && assertNodeType(tView.node!, TNodeType.View);
const viewRootTNode: TNode|null = tView.node !.child; const viewRootTNode: TNode|null = tView.node!.child;
applyNodes(renderer, action, viewRootTNode, lView, renderParent, beforeNode, false); applyNodes(renderer, action, viewRootTNode, lView, renderParent, beforeNode, false);
} }
@ -833,7 +846,7 @@ function applyView(
export function applyProjection(tView: TView, lView: LView, tProjectionNode: TProjectionNode) { export function applyProjection(tView: TView, lView: LView, tProjectionNode: TProjectionNode) {
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
const renderParent = getRenderParent(tView, tProjectionNode, lView); const renderParent = getRenderParent(tView, tProjectionNode, lView);
const parentTNode = tProjectionNode.parent || lView[T_HOST] !; const parentTNode = tProjectionNode.parent || lView[T_HOST]!;
let beforeNode = getNativeAnchorNode(parentTNode, lView); let beforeNode = getNativeAnchorNode(parentTNode, lView);
applyProjectionRecursive( applyProjectionRecursive(
renderer, WalkTNodeTreeAction.Create, lView, tProjectionNode, renderParent, beforeNode); renderer, WalkTNodeTreeAction.Create, lView, tProjectionNode, renderParent, beforeNode);
@ -855,12 +868,12 @@ export function applyProjection(tView: TView, lView: LView, tProjectionNode: TPr
*/ */
function applyProjectionRecursive( function applyProjectionRecursive(
renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView,
tProjectionNode: TProjectionNode, renderParent: RElement | null, beforeNode: RNode | null) { tProjectionNode: TProjectionNode, renderParent: RElement|null, beforeNode: RNode|null) {
const componentLView = lView[DECLARATION_COMPONENT_VIEW]; const componentLView = lView[DECLARATION_COMPONENT_VIEW];
const componentNode = componentLView[T_HOST] as TElementNode; const componentNode = componentLView[T_HOST] as TElementNode;
ngDevMode && ngDevMode &&
assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index'); assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index');
const nodeToProjectOrRNodes = componentNode.projection ![tProjectionNode.projection] !; const nodeToProjectOrRNodes = componentNode.projection![tProjectionNode.projection]!;
if (Array.isArray(nodeToProjectOrRNodes)) { if (Array.isArray(nodeToProjectOrRNodes)) {
// This should not exist, it is a bit of a hack. When we bootstrap a top level node and we // This should not exist, it is a bit of a hack. When we bootstrap a top level node and we
// need to support passing projectable nodes, so we cheat and put them in the TNode // need to support passing projectable nodes, so we cheat and put them in the TNode
@ -895,7 +908,7 @@ function applyProjectionRecursive(
*/ */
function applyContainer( function applyContainer(
renderer: Renderer3, action: WalkTNodeTreeAction, lContainer: LContainer, renderer: Renderer3, action: WalkTNodeTreeAction, lContainer: LContainer,
renderParent: RElement | null, beforeNode: RNode | null | undefined) { renderParent: RElement|null, beforeNode: RNode|null|undefined) {
ngDevMode && assertLContainer(lContainer); ngDevMode && assertLContainer(lContainer);
const anchor = lContainer[NATIVE]; // LContainer has its own before node. const anchor = lContainer[NATIVE]; // LContainer has its own before node.
const native = unwrapRNode(lContainer); const native = unwrapRNode(lContainer);
@ -1016,4 +1029,4 @@ export function writeDirectClass(renderer: Renderer3, element: RElement, newValu
element.className = newValue; element.className = newValue;
} }
ngDevMode && ngDevMode.rendererSetClassName++; ngDevMode && ngDevMode.rendererSetClassName++;
} }

View File

@ -6,16 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, Directive, Inject, Injectable, InjectionToken, Injector, NgModule, Optional, forwardRef} from '@angular/core'; import {Component, Directive, forwardRef, Inject, Injectable, InjectionToken, Injector, NgModule, Optional} from '@angular/core';
import {TestBed, async, inject} from '@angular/core/testing'; import {async, inject, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser'; import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing'; import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
describe('providers', () => { describe('providers', () => {
describe('inheritance', () => { describe('inheritance', () => {
it('should NOT inherit providers', () => { it('should NOT inherit providers', () => {
const SOME_DIRS = new InjectionToken('someDirs'); const SOME_DIRS = new InjectionToken('someDirs');
@ -52,7 +50,6 @@ describe('providers', () => {
expect(otherDir.dirs.length).toEqual(1); expect(otherDir.dirs.length).toEqual(1);
expect(otherDir.dirs[0] instanceof SubDirective).toBe(true); expect(otherDir.dirs[0] instanceof SubDirective).toBe(true);
}); });
}); });
describe('lifecycles', () => { describe('lifecycles', () => {
@ -61,7 +58,9 @@ describe('providers', () => {
@Injectable() @Injectable()
class SuperInjectableWithDestroyHook { class SuperInjectableWithDestroyHook {
ngOnDestroy() { logs.push('OnDestroy'); } ngOnDestroy() {
logs.push('OnDestroy');
}
} }
@Injectable() @Injectable()
@ -86,7 +85,9 @@ describe('providers', () => {
@Injectable() @Injectable()
class InjectableWithDestroyHook { class InjectableWithDestroyHook {
ngOnDestroy() { logs.push('OnDestroy'); } ngOnDestroy() {
logs.push('OnDestroy');
}
} }
@Component({template: '', providers: [InjectableWithDestroyHook]}) @Component({template: '', providers: [InjectableWithDestroyHook]})
@ -106,7 +107,9 @@ describe('providers', () => {
@Injectable() @Injectable()
class InjectableWithDestroyHook { class InjectableWithDestroyHook {
ngOnDestroy() { logs.push('OnDestroy'); } ngOnDestroy() {
logs.push('OnDestroy');
}
} }
@Component({selector: 'my-cmp', template: ''}) @Component({selector: 'my-cmp', template: ''})
@ -137,7 +140,9 @@ describe('providers', () => {
@Injectable() @Injectable()
class InjectableWithDestroyHook { class InjectableWithDestroyHook {
ngOnDestroy() { logs.push('OnDestroy'); } ngOnDestroy() {
logs.push('OnDestroy');
}
} }
@Component({ @Component({
@ -162,12 +167,16 @@ describe('providers', () => {
@Injectable() @Injectable()
class InjectableWithDestroyHookToken { class InjectableWithDestroyHookToken {
ngOnDestroy() { logs.push('OnDestroy Token'); } ngOnDestroy() {
logs.push('OnDestroy Token');
}
} }
@Injectable() @Injectable()
class InjectableWithDestroyHookValue { class InjectableWithDestroyHookValue {
ngOnDestroy() { logs.push('OnDestroy Value'); } ngOnDestroy() {
logs.push('OnDestroy Value');
}
} }
@Component({ @Component({
@ -193,21 +202,23 @@ describe('providers', () => {
@Injectable() @Injectable()
class InjectableWithDestroyHookToken { class InjectableWithDestroyHookToken {
ngOnDestroy() { logs.push('OnDestroy Token'); } ngOnDestroy() {
logs.push('OnDestroy Token');
}
} }
@Injectable() @Injectable()
class InjectableWithDestroyHookExisting { class InjectableWithDestroyHookExisting {
ngOnDestroy() { logs.push('OnDestroy Existing'); } ngOnDestroy() {
logs.push('OnDestroy Existing');
}
} }
@Component({ @Component({
template: '', template: '',
providers: [ providers: [
InjectableWithDestroyHookExisting, { InjectableWithDestroyHookExisting,
provide: InjectableWithDestroyHookToken, {provide: InjectableWithDestroyHookToken, useExisting: InjectableWithDestroyHookExisting}
useExisting: InjectableWithDestroyHookExisting
}
] ]
}) })
class App { class App {
@ -225,30 +236,40 @@ describe('providers', () => {
it('should invoke ngOnDestroy with the correct context when providing a type provider multiple times on the same node', it('should invoke ngOnDestroy with the correct context when providing a type provider multiple times on the same node',
() => { () => {
const resolvedServices: (DestroyService | undefined)[] = []; const resolvedServices: (DestroyService|undefined)[] = [];
const destroyContexts: (DestroyService | undefined)[] = []; const destroyContexts: (DestroyService|undefined)[] = [];
let parentService: DestroyService|undefined; let parentService: DestroyService|undefined;
let childService: DestroyService|undefined; let childService: DestroyService|undefined;
@Injectable() @Injectable()
class DestroyService { class DestroyService {
constructor() { resolvedServices.push(this); } constructor() {
ngOnDestroy() { destroyContexts.push(this); } resolvedServices.push(this);
}
ngOnDestroy() {
destroyContexts.push(this);
}
} }
@Directive({selector: '[dir-one]', providers: [DestroyService]}) @Directive({selector: '[dir-one]', providers: [DestroyService]})
class DirOne { class DirOne {
constructor(service: DestroyService) { childService = service; } constructor(service: DestroyService) {
childService = service;
}
} }
@Directive({selector: '[dir-two]', providers: [DestroyService]}) @Directive({selector: '[dir-two]', providers: [DestroyService]})
class DirTwo { class DirTwo {
constructor(service: DestroyService) { childService = service; } constructor(service: DestroyService) {
childService = service;
}
} }
@Component({template: '<div dir-one dir-two></div>', providers: [DestroyService]}) @Component({template: '<div dir-one dir-two></div>', providers: [DestroyService]})
class App { class App {
constructor(service: DestroyService) { parentService = service; } constructor(service: DestroyService) {
parentService = service;
}
} }
TestBed.configureTestingModule({declarations: [App, DirOne, DirTwo]}); TestBed.configureTestingModule({declarations: [App, DirOne, DirTwo]});
@ -266,28 +287,36 @@ describe('providers', () => {
onlyInIvy('Destroy hook of useClass provider is invoked correctly') onlyInIvy('Destroy hook of useClass provider is invoked correctly')
.it('should invoke ngOnDestroy with the correct context when providing a class provider multiple times on the same node', .it('should invoke ngOnDestroy with the correct context when providing a class provider multiple times on the same node',
() => { () => {
const resolvedServices: (DestroyService | undefined)[] = []; const resolvedServices: (DestroyService|undefined)[] = [];
const destroyContexts: (DestroyService | undefined)[] = []; const destroyContexts: (DestroyService|undefined)[] = [];
const token = new InjectionToken<any>('token'); const token = new InjectionToken<any>('token');
let parentService: DestroyService|undefined; let parentService: DestroyService|undefined;
let childService: DestroyService|undefined; let childService: DestroyService|undefined;
@Injectable() @Injectable()
class DestroyService { class DestroyService {
constructor() { resolvedServices.push(this); } constructor() {
ngOnDestroy() { destroyContexts.push(this); } resolvedServices.push(this);
}
ngOnDestroy() {
destroyContexts.push(this);
}
} }
@Directive( @Directive(
{selector: '[dir-one]', providers: [{provide: token, useClass: DestroyService}]}) {selector: '[dir-one]', providers: [{provide: token, useClass: DestroyService}]})
class DirOne { class DirOne {
constructor(@Inject(token) service: DestroyService) { childService = service; } constructor(@Inject(token) service: DestroyService) {
childService = service;
}
} }
@Directive( @Directive(
{selector: '[dir-two]', providers: [{provide: token, useClass: DestroyService}]}) {selector: '[dir-two]', providers: [{provide: token, useClass: DestroyService}]})
class DirTwo { class DirTwo {
constructor(@Inject(token) service: DestroyService) { childService = service; } constructor(@Inject(token) service: DestroyService) {
childService = service;
}
} }
@Component({ @Component({
@ -295,7 +324,9 @@ describe('providers', () => {
providers: [{provide: token, useClass: DestroyService}] providers: [{provide: token, useClass: DestroyService}]
}) })
class App { class App {
constructor(@Inject(token) service: DestroyService) { parentService = service; } constructor(@Inject(token) service: DestroyService) {
parentService = service;
}
} }
TestBed.configureTestingModule({declarations: [App, DirOne, DirTwo]}); TestBed.configureTestingModule({declarations: [App, DirOne, DirTwo]});
@ -310,21 +341,151 @@ describe('providers', () => {
expect(destroyContexts).toEqual([parentService, childService]); expect(destroyContexts).toEqual([parentService, childService]);
}); });
onlyInIvy('ngOnDestroy hooks for multi providers were not supported in ViewEngine') onlyInIvy('ngOnDestroy hooks for multi providers were not supported in ViewEngine')
.describe('ngOnDestroy on multi providers', () => {
it('should invoke ngOnDestroy on multi providers with the correct context', () => {
const destroyCalls: any[] = [];
const SERVICES = new InjectionToken<any>('SERVICES');
@Injectable()
class DestroyService {
ngOnDestroy() {
destroyCalls.push(this);
}
}
@Injectable()
class OtherDestroyService {
ngOnDestroy() {
destroyCalls.push(this);
}
}
@Component({
template: '<div></div>',
providers: [
{provide: SERVICES, useClass: DestroyService, multi: true},
{provide: SERVICES, useClass: OtherDestroyService, multi: true},
]
})
class App {
constructor(@Inject(SERVICES) s: any) {}
}
TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.destroy();
expect(destroyCalls).toEqual([
jasmine.any(DestroyService), jasmine.any(OtherDestroyService)
]);
});
it('should invoke destroy hooks on multi providers with the correct context, if only some have a destroy hook',
() => {
const destroyCalls: any[] = [];
const SERVICES = new InjectionToken<any>('SERVICES');
@Injectable()
class Service1 {
}
@Injectable()
class Service2 {
ngOnDestroy() {
destroyCalls.push(this);
}
}
@Injectable()
class Service3 {
}
@Injectable()
class Service4 {
ngOnDestroy() {
destroyCalls.push(this);
}
}
@Component({
template: '<div></div>',
providers: [
{provide: SERVICES, useClass: Service1, multi: true},
{provide: SERVICES, useClass: Service2, multi: true},
{provide: SERVICES, useClass: Service3, multi: true},
{provide: SERVICES, useClass: Service4, multi: true},
]
})
class App {
constructor(@Inject(SERVICES) s: any) {}
}
TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.destroy();
expect(destroyCalls).toEqual([jasmine.any(Service2), jasmine.any(Service4)]);
});
it('should not invoke ngOnDestroy on multi providers created via useFactory', () => {
let destroyCalls = 0;
const SERVICES = new InjectionToken<any>('SERVICES');
@Injectable()
class DestroyService {
ngOnDestroy() {
destroyCalls++;
}
}
@Injectable()
class OtherDestroyService {
ngOnDestroy() {
destroyCalls++;
}
}
@Component({
template: '<div></div>',
providers: [
{provide: SERVICES, useFactory: () => new DestroyService(), multi: true},
{provide: SERVICES, useFactory: () => new OtherDestroyService(), multi: true},
]
})
class App {
constructor(@Inject(SERVICES) s: any) {}
}
TestBed.configureTestingModule({declarations: [App]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.destroy();
expect(destroyCalls).toBe(0);
});
});
modifiedInIvy('ViewEngine did not support destroy hooks on multi providers')
.it('should not invoke ngOnDestroy on multi providers', () => { .it('should not invoke ngOnDestroy on multi providers', () => {
// TODO(FW-1866): currently we only assert that the hook was called,
// but we should also be checking that the correct context was passed in.
let destroyCalls = 0; let destroyCalls = 0;
const SERVICES = new InjectionToken<any>('SERVICES'); const SERVICES = new InjectionToken<any>('SERVICES');
@Injectable() @Injectable()
class DestroyService { class DestroyService {
ngOnDestroy() { destroyCalls++; } ngOnDestroy() {
destroyCalls++;
}
} }
@Injectable() @Injectable()
class OtherDestroyService { class OtherDestroyService {
ngOnDestroy() { destroyCalls++; } ngOnDestroy() {
destroyCalls++;
}
} }
@Component({ @Component({
@ -343,13 +504,11 @@ describe('providers', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.destroy(); fixture.destroy();
expect(destroyCalls).toBe(2); expect(destroyCalls).toBe(0);
}); });
}); });
describe('components and directives', () => { describe('components and directives', () => {
class MyService { class MyService {
value = 'some value'; value = 'some value';
} }
@ -409,7 +568,6 @@ describe('providers', () => {
}); });
describe('forward refs', () => { describe('forward refs', () => {
it('should support forward refs in provider deps', () => { it('should support forward refs in provider deps', () => {
class MyService { class MyService {
constructor(public dep: {value: string}) {} constructor(public dep: {value: string}) {}
@ -444,7 +602,6 @@ describe('providers', () => {
}); });
it('should support forward refs in useClass when impl version is also provided', () => { it('should support forward refs in useClass when impl version is also provided', () => {
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)}) @Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
abstract class SomeProvider { abstract class SomeProvider {
} }
@ -490,11 +647,9 @@ describe('providers', () => {
expect(fixture.componentInstance.foo).toBeAnInstanceOf(SomeProviderImpl); expect(fixture.componentInstance.foo).toBeAnInstanceOf(SomeProviderImpl);
}); });
}); });
describe('flags', () => { describe('flags', () => {
class MyService { class MyService {
constructor(public value: OtherService|null) {} constructor(public value: OtherService|null) {}
} }