diff --git a/aio/scripts/_payload-limits.json b/aio/scripts/_payload-limits.json index e83f14b8a1..e10ce7bf43 100755 --- a/aio/scripts/_payload-limits.json +++ b/aio/scripts/_payload-limits.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 2987, - "main-es2015": 451469, + "main-es2015": 452060, "polyfills-es2015": 52195 } } diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 6d1421d89f..0bff4575c2 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime-es2015": 1485, - "main-es2015": 141569, + "main-es2015": 142073, "polyfills-es2015": 36657 } } @@ -21,7 +21,7 @@ "master": { "uncompressed": { "runtime-es2015": 1485, - "main-es2015": 147647, + "main-es2015": 148196, "polyfills-es2015": 36657 } } diff --git a/packages/core/src/render3/di_setup.ts b/packages/core/src/render3/di_setup.ts index 483ae76f44..5119bc3706 100644 --- a/packages/core/src/render3/di_setup.ts +++ b/packages/core/src/render3/di_setup.ts @@ -10,6 +10,7 @@ import {resolveForwardRef} from '../di/forward_ref'; import {ClassProvider, Provider} from '../di/interface/provider'; import {isClassProvider, isTypeProvider, providerToFactory} from '../di/r3_injector'; +import {assertDefined} from '../util/assert'; import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from './di'; import {ɵɵdirectiveInject} from './instructions/all'; @@ -17,7 +18,7 @@ import {DirectiveDef} from './interfaces/definition'; import {NodeInjectorFactory} from './interfaces/injector'; import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNodeProviderIndexes} from './interfaces/node'; 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'; @@ -149,7 +150,7 @@ function resolveProvider( if (!isViewProvider && doesViewProvidersFactoryExist) { lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory; } - registerDestroyHooksIfSupported(tView, provider, tInjectables.length); + registerDestroyHooksIfSupported(tView, provider, tInjectables.length, 0); tInjectables.push(token); tNode.directiveStart++; tNode.directiveEnd++; @@ -160,16 +161,19 @@ function resolveProvider( lView.push(factory); } else { // Cases 1.b and 2.b - registerDestroyHooksIfSupported( - tView, provider, existingProvidersFactoryIndex > -1 ? - existingProvidersFactoryIndex : - existingViewProvidersFactoryIndex); - multiFactoryAdd( - lInjectablesBlueprint ![isViewProvider ? existingViewProvidersFactoryIndex : existingProvidersFactoryIndex], + const indexInFactory = multiFactoryAdd( + lInjectablesBlueprint! + [isViewProvider ? existingViewProvidersFactoryIndex : + existingProvidersFactoryIndex], providerFactory, !isViewProvider && isComponent); + registerDestroyHooksIfSupported( + tView, provider, + existingProvidersFactoryIndex > -1 ? existingProvidersFactoryIndex : + existingViewProvidersFactoryIndex, + indexInFactory); } 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 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 indexInFactory Only required for `multi` providers. Index of the provider in the multi + * provider factory. */ function registerDestroyHooksIfSupported( - tView: TView, provider: Exclude, contextIndex: number) { + tView: TView, provider: Exclude, contextIndex: number, + indexInFactory?: number) { const providerIsTypeProvider = isTypeProvider(provider); if (providerIsTypeProvider || isClassProvider(provider)) { const prototype = ((provider as ClassProvider).useClass || provider).prototype; const ngOnDestroy = prototype.ngOnDestroy; if (ngOnDestroy) { - (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. + * @returns Index at which the factory was inserted. */ function multiFactoryAdd( - multiFactory: NodeInjectorFactory, factory: () => any, isComponentProvider: boolean): void { - multiFactory.multi !.push(factory); + multiFactory: NodeInjectorFactory, factory: () => any, isComponentProvider: boolean): number { 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( this: NodeInjectorFactory, _: undefined, tData: TData, lData: LView, tNode: TDirectiveHostNode): any[] { - return multiResolve(this.multi !, []); + return multiResolve(this.multi!, []); } /** @@ -231,12 +254,12 @@ function multiProvidersFactoryResolver( function multiViewProvidersFactoryResolver( this: NodeInjectorFactory, _: undefined, tData: TData, lView: LView, tNode: TDirectiveHostNode): any[] { - const factories = this.multi !; + const factories = this.multi!; let result: any[]; if (this.providerFactory) { - const componentCount = this.providerFactory.componentProviders !; + const componentCount = this.providerFactory.componentProviders!; 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 result = multiProviders.slice(0, componentCount); // Insert the `viewProvider` instances. @@ -258,7 +281,7 @@ function multiViewProvidersFactoryResolver( */ function multiResolve(factories: Array<() => any>, result: any[]): any[] { for (let i = 0; i < factories.length; i++) { - const factory = factories[i] !as() => null; + const factory = factories[i]! as () => null; result.push(factory()); } return result; diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index d2b513dc3c..fd3030084a 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -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 {SelectorFlags} from '../interfaces/projection'; import {LQueries, TQueries} from '../interfaces/query'; -import {RComment, RElement, RNode, Renderer3, RendererFactory3} from '../interfaces/renderer'; -import {TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate} 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 {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer'; +import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling'; +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 {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>; -let LVIEW_EMBEDDED_CACHE !: Map>; -let LVIEW_ROOT !: Array; +let LVIEW_COMPONENT_CACHE!: Map>; +let LVIEW_EMBEDDED_CACHE!: Map>; +let LVIEW_ROOT!: Array; interface TViewDebug extends ITView { type: TViewType; @@ -75,7 +75,7 @@ export function cloneToLViewFromTViewBlueprint(tView: TView): LView { return lView.concat(tView.blueprint) as any; } -function getLViewToClone(type: TViewType, name: string | null): Array { +function getLViewToClone(type: TViewType, name: string|null): Array { switch (type) { case TViewType.Root: if (LVIEW_ROOT === undefined) LVIEW_ROOT = new (createNamedArrayType('LRootView'))(); @@ -100,7 +100,7 @@ function getLViewToClone(type: TViewType, name: string | null): Array { throw new Error('unreachable code'); } -function nameSuffix(text: string | null | undefined): string { +function nameSuffix(text: string|null|undefined): string { if (text == null) return ''; const index = text.lastIndexOf('_Template'); return '_' + (index === -1 ? text : text.substr(0, index)); @@ -134,7 +134,7 @@ export const TViewConstructor = class TView implements ITView { public contentCheckHooks: HookData|null, // public viewHooks: HookData|null, // public viewCheckHooks: HookData|null, // - public destroyHooks: HookData|null, // + public destroyHooks: DestroyHookData|null, // public cleanup: any[]|null, // public contentQueries: number[]|null, // public components: number[]|null, // @@ -143,7 +143,7 @@ export const TViewConstructor = class TView implements ITView { public firstChild: ITNode|null, // public schemas: SchemaMetadata[]|null, // public consts: TConstants|null, // - ) {} + ) {} get template_(): string { const buf: string[] = []; @@ -183,7 +183,7 @@ class TNode implements ITNode { public residualClasses: KeyValueArray|undefined|null, // public classBindings: TStylingRange, // public styleBindings: TStylingRange, // - ) {} + ) {} get type_(): string { switch (this.type) { @@ -236,8 +236,12 @@ class TNode implements ITNode { return buf.join(''); } - get styleBindings_(): DebugStyleBindings { return toDebugStyleBinding(this, false); } - get classBindings_(): DebugStyleBindings { return toDebugStyleBinding(this, true); } + get styleBindings_(): DebugStyleBindings { + return toDebugStyleBinding(this, false); + } + get classBindings_(): DebugStyleBindings { + return toDebugStyleBinding(this, true); + } } export const TNodeDebug = TNode; export type TNodeDebug = TNode; @@ -281,17 +285,16 @@ function toDebugStyleBinding(tNode: TNode, isClassBased: boolean): DebugStyleBin return bindings; } -function processTNodeChildren(tNode: ITNode | null, buf: string[]) { +function processTNodeChildren(tNode: ITNode|null, buf: string[]) { while (tNode) { - buf.push((tNode as any as{template_: string}).template_); + buf.push((tNode as any as {template_: string}).template_); tNode = tNode.next; } } -const TViewData = NG_DEV_MODE && createNamedArrayType('TViewData') || null !as ArrayConstructor; -let TVIEWDATA_EMPTY: - unknown[]; // can't initialize here or it will not be tree shaken, because `LView` - // constructor could have side-effects. +const TViewData = NG_DEV_MODE && createNamedArrayType('TViewData') || null! as ArrayConstructor; +let TVIEWDATA_EMPTY: unknown[]; // can't initialize here or it will not be tree shaken, because + // `LView` constructor could have side-effects. /** * This function clones a blueprint and creates TData. * @@ -303,21 +306,21 @@ export function cloneToTViewData(list: any[]): TData { } export const LViewBlueprint = - NG_DEV_MODE && createNamedArrayType('LViewBlueprint') || null !as ArrayConstructor; + NG_DEV_MODE && createNamedArrayType('LViewBlueprint') || null! as ArrayConstructor; export const MatchesArray = - NG_DEV_MODE && createNamedArrayType('MatchesArray') || null !as ArrayConstructor; + NG_DEV_MODE && createNamedArrayType('MatchesArray') || null! as ArrayConstructor; export const TViewComponents = - NG_DEV_MODE && createNamedArrayType('TViewComponents') || null !as ArrayConstructor; + NG_DEV_MODE && createNamedArrayType('TViewComponents') || null! as ArrayConstructor; export const TNodeLocalNames = - NG_DEV_MODE && createNamedArrayType('TNodeLocalNames') || null !as ArrayConstructor; + NG_DEV_MODE && createNamedArrayType('TNodeLocalNames') || null! as ArrayConstructor; export const TNodeInitialInputs = - NG_DEV_MODE && createNamedArrayType('TNodeInitialInputs') || null !as ArrayConstructor; + NG_DEV_MODE && createNamedArrayType('TNodeInitialInputs') || null! as ArrayConstructor; export const TNodeInitialData = - NG_DEV_MODE && createNamedArrayType('TNodeInitialData') || null !as ArrayConstructor; + NG_DEV_MODE && createNamedArrayType('TNodeInitialData') || null! as ArrayConstructor; export const LCleanup = - NG_DEV_MODE && createNamedArrayType('LCleanup') || null !as ArrayConstructor; + NG_DEV_MODE && createNamedArrayType('LCleanup') || null! as ArrayConstructor; 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 | null): LViewDebug|null; -export function toDebug(obj: LView | LContainer | null): LViewDebug|LContainerDebug|null; +export function toDebug(obj: LView|null): LViewDebug|null; +export function toDebug(obj: LView|LContainer|null): LViewDebug|LContainerDebug|null; export function toDebug(obj: any): any { if (obj) { const debug = (obj as any).debug; @@ -390,10 +393,18 @@ export class LViewDebug { indexWithinInitPhase: flags >> LViewFlags.IndexWithinInitPhaseShift, }; } - get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lView[PARENT]); } - 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]; } + get parent(): LViewDebug|LContainerDebug|null { + return toDebug(this._raw_lView[PARENT]); + } + 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 * a @@ -405,18 +416,42 @@ export class LViewDebug { return toDebugNodes(tNode, lView); } - get tView(): ITView { return this._raw_lView[TVIEW]; } - get cleanup(): any[]|null { return this._raw_lView[CLEANUP]; } - get injector(): Injector|null { return this._raw_lView[INJECTOR]; } - get rendererFactory(): RendererFactory3 { return this._raw_lView[RENDERER_FACTORY]; } - 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]; } + get tView(): ITView { + return this._raw_lView[TVIEW]; + } + get cleanup(): any[]|null { + return this._raw_lView[CLEANUP]; + } + get injector(): Injector|null { + return this._raw_lView[INJECTOR]; + } + get rendererFactory(): RendererFactory3 { + return this._raw_lView[RENDERER_FACTORY]; + } + 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. @@ -445,7 +480,7 @@ export interface DebugNode { * @param tNode * @param lView */ -export function toDebugNodes(tNode: ITNode | null, lView: LView): DebugNode[]|null { +export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[]|null { if (tNode) { const debugNodes: DebugNode[] = []; let tNodeCursor: ITNode|null = tNode; @@ -474,20 +509,32 @@ export function buildDebugNode(tNode: ITNode, lView: LView, nodeIndex: number): export class LContainerDebug { 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 { return (this._raw_lContainer[ACTIVE_INDEX] & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) === ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS; } get views(): LViewDebug[] { 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 { [key: string]: any; - get tNode() { return getTNode(this._lView[TVIEW], this.nodeIndex); } + get tNode() { + return getTNode(this._lView[TVIEW], this.nodeIndex); + } constructor( 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 */ export function attachI18nOpCodesDebug( - mutateOpCodes: I18nMutateOpCodes, updateOpCodes: I18nUpdateOpCodes, icus: TIcu[] | null, + mutateOpCodes: I18nMutateOpCodes, updateOpCodes: I18nUpdateOpCodes, icus: TIcu[]|null, lView: LView) { attachDebugObject(mutateOpCodes, new I18nMutateOpCodesDebug(mutateOpCodes, lView)); attachDebugObject(updateOpCodes, new I18nUpdateOpCodesDebug(updateOpCodes, icus, lView)); if (icus) { icus.forEach(icu => { - icu.create.forEach( - icuCase => { attachDebugObject(icuCase, new I18nMutateOpCodesDebug(icuCase, lView)); }); + icu.create.forEach(icuCase => { + attachDebugObject(icuCase, new I18nMutateOpCodesDebug(icuCase, lView)); + }); icu.update.forEach(icuCase => { attachDebugObject(icuCase, new I18nUpdateOpCodesDebug(icuCase, icus, lView)); }); @@ -645,7 +695,7 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug { if (opCode < 0) { // It's a binding index whose value is negative // We cannot know the value of the binding so we only show the index - value += `�${-opCode - 1}�`; + value += `�${- opCode - 1}�`; } else { const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF; let tIcuIndex: number; @@ -658,20 +708,23 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug { __raw_opCode: opCode, checkBit, type: 'Attr', - attrValue: value, attrName, sanitizeFn, + attrValue: value, + attrName, + sanitizeFn, }); break; case I18nUpdateOpCode.Text: results.push({ __raw_opCode: opCode, checkBit, - type: 'Text', nodeIndex, + type: 'Text', + nodeIndex, text: value, }); break; case I18nUpdateOpCode.IcuSwitch: tIcuIndex = __raw_opCodes[++j] as number; - tIcu = icus ![tIcuIndex]; + tIcu = icus![tIcuIndex]; let result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuSwitch'); result['tIcuIndex'] = tIcuIndex; result['checkBit'] = checkBit; @@ -681,7 +734,7 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug { break; case I18nUpdateOpCode.IcuUpdate: tIcuIndex = __raw_opCodes[++j] as number; - tIcu = icus ![tIcuIndex]; + tIcu = icus![tIcuIndex]; result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuUpdate'); result['tIcuIndex'] = tIcuIndex; result['checkBit'] = checkBit; @@ -698,4 +751,6 @@ export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug { } } -export interface I18nOpCodesDebug { operations: any[]; } +export interface I18nOpCodesDebug { + operations: any[]; +} diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 5c07128a56..deedddd0cc 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -24,11 +24,11 @@ import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} fr 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 {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 {RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer'; +import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node'; +import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer'; import {SanitizerFn} from '../interfaces/sanitization'; 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 {isNodeMatchingSelectorList} from '../node_selector_matcher'; 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 {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 && assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined'); 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 * @returns the element created */ -export function elementCreate( - name: string, renderer: Renderer3, namespace: string | null): RElement { +export function elementCreate(name: string, renderer: Renderer3, namespace: string|null): RElement { if (isProceduralRenderer(renderer)) { return renderer.createElement(name, namespace); } else { @@ -166,10 +165,9 @@ export function elementCreate( } export function createLView( - parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags, - host: RElement | null, tHostNode: TViewNode | TElementNode | null, - rendererFactory?: RendererFactory3 | null, renderer?: Renderer3 | null, - sanitizer?: Sanitizer | null, injector?: Injector | null): LView { + parentLView: LView|null, tView: TView, context: T|null, flags: LViewFlags, host: RElement|null, + tHostNode: TViewNode|TElementNode|null, rendererFactory?: RendererFactory3|null, + renderer?: Renderer3|null, sanitizer?: Sanitizer|null, injector?: Injector|null): LView { const lView = ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : tView.blueprint.slice() as LView; lView[HOST] = host; @@ -177,18 +175,19 @@ export function createLView( resetPreOrderHookFlags(lView); lView[PARENT] = lView[DECLARATION_VIEW] = parentLView; 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'); - lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER]) !; + lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER])!; 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[T_HOST] = tHostNode; - ngDevMode && assertEqual( - tView.type == TViewType.Embedded ? parentLView !== null : true, true, - 'Embedded views must have parentLView'); + ngDevMode && + assertEqual( + tView.type == TViewType.Embedded ? parentLView !== null : true, true, + 'Embedded views must have parentLView'); 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); return lView; } @@ -208,23 +207,23 @@ export function createLView( * @param attrs Any attrs for the native element, if applicable */ export function getOrCreateTNode( - tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.Element, - name: string | null, attrs: TAttributes | null): TElementNode; + tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.Element, name: string|null, + attrs: TAttributes|null): TElementNode; export function getOrCreateTNode( - tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.Container, - name: string | null, attrs: TAttributes | null): TContainerNode; + tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.Container, + name: string|null, attrs: TAttributes|null): TContainerNode; export function getOrCreateTNode( - tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.Projection, name: null, - attrs: TAttributes | null): TProjectionNode; + tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.Projection, name: null, + attrs: TAttributes|null): TProjectionNode; export function getOrCreateTNode( - tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.ElementContainer, - name: string | null, attrs: TAttributes | null): TElementContainerNode; + tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.ElementContainer, + name: string|null, attrs: TAttributes|null): TElementContainerNode; export function getOrCreateTNode( - tView: TView, tHostNode: TNode | null, index: number, type: TNodeType.IcuContainer, name: null, - attrs: TAttributes | null): TElementContainerNode; + tView: TView, tHostNode: TNode|null, index: number, type: TNodeType.IcuContainer, name: null, + attrs: TAttributes|null): TElementContainerNode; export function getOrCreateTNode( - tView: TView, tHostNode: TNode | null, index: number, type: TNodeType, name: string | null, - attrs: TAttributes | null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode& + tView: TView, tHostNode: TNode|null, index: number, type: TNodeType, name: string|null, + attrs: TAttributes|null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode& TIcuContainerNode { // Keep this function short, so that the VM will inline it. const adjustedIndex = index + HEADER_OFFSET; @@ -236,8 +235,8 @@ export function getOrCreateTNode( } function createTNodeAtIndex( - tView: TView, tHostNode: TNode | null, adjustedIndex: number, type: TNodeType, - name: string | null, attrs: TAttributes | null) { + tView: TView, tHostNode: TNode|null, adjustedIndex: number, type: TNodeType, name: string|null, + attrs: TAttributes|null) { const previousOrParentTNode = getPreviousOrParentTNode(); const isParent = getIsParent(); const parent = @@ -267,7 +266,7 @@ function createTNodeAtIndex( } 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 // would cause indices to change). Their TNodes are instead stored in tView.node. let tNode = tView.node; @@ -275,9 +274,9 @@ export function assignTViewNodeToLView( ngDevMode && tParentNode && assertNodeOfPossibleTypes(tParentNode, TNodeType.Element, TNodeType.Container); tView.node = tNode = createTNode( - tView, - tParentNode as TElementNode | TContainerNode | null, // - TNodeType.View, index, null, null) as TViewNode; + tView, + tParentNode as TElementNode | TContainerNode | null, // + TNodeType.View, index, null, null) 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 */ export function allocExpando(tView: TView, lView: LView, numSlotsToAlloc: number) { - ngDevMode && assertGreaterThan( - numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0'); + ngDevMode && + assertGreaterThan( + numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0'); if (numSlotsToAlloc > 0) { if (tView.firstCreatePass) { for (let i = 0; i < numSlotsToAlloc; i++) { @@ -365,7 +365,7 @@ export function renderView(tView: TView, lView: LView, context: T): void { // in case a child component has projected a container. The LContainer needs // to exist so the embedded views are properly attached by the container. if (tView.staticViewQueries) { - executeViewQueryFn(RenderFlags.Update, tView.viewQuery !, context); + executeViewQueryFn(RenderFlags.Update, tView.viewQuery!, context); } // Render child component views. @@ -389,7 +389,7 @@ export function renderView(tView: TView, lView: LView, context: T): void { * - refreshing child (embedded and component) views. */ export function refreshView( - 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'); const flags = lView[FLAGS]; if ((flags & LViewFlags.Destroyed) === LViewFlags.Destroyed) return; @@ -505,7 +505,7 @@ export function refreshView( } export function renderComponentOrTemplate( - tView: TView, lView: LView, templateFn: ComponentTemplate<{}>| null, context: T) { + tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) { const rendererFactory = lView[RENDERER_FACTORY]; const normalExecutionPath = !getCheckNoChangesMode(); const creationModeIsActive = isCreationMode(lView); @@ -618,10 +618,10 @@ export function getOrCreateTComponentView(def: ComponentDef): TView { * @param consts Constants for this view */ export function createTView( - type: TViewType, viewIndex: number, templateFn: ComponentTemplate| null, decls: number, - vars: number, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, - viewQuery: ViewQueriesFunction| null, schemas: SchemaMetadata[] | null, - consts: TConstants | null): TView { + type: TViewType, viewIndex: number, templateFn: ComponentTemplate|null, decls: number, + vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null, + viewQuery: ViewQueriesFunction|null, schemas: SchemaMetadata[]|null, + consts: TConstants|null): TView { ngDevMode && ngDevMode.tView++; const bindingStartIndex = HEADER_OFFSET + decls; // 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, null, // queries: TQueries|null viewQuery, // viewQuery: ViewQueriesFunction<{}>|null, - null !, // node: TViewNode|TElementNode|null, + null!, // node: TViewNode|TElementNode|null, cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData, bindingStartIndex, // bindingStartIndex: number, initialViewLength, // expandoStartIndex: number, @@ -652,7 +652,7 @@ export function createTView( null, // contentCheckHooks: HookData|null, null, // viewHooks: HookData|null, null, // viewCheckHooks: HookData|null, - null, // destroyHooks: HookData|null, + null, // destroyHooks: DestroyHookData|null, null, // cleanup: any[]|null, null, // contentQueries: number[]|null, null, // components: number[]|null, @@ -670,7 +670,7 @@ export function createTView( template: templateFn, queries: null, viewQuery: viewQuery, - node: null !, + node: null!, data: blueprint.slice().fill(null, bindingStartIndex), bindingStartIndex: bindingStartIndex, expandoStartIndex: initialViewLength, @@ -711,7 +711,7 @@ function createError(text: string, token: any) { return new Error(`Renderer: ${text} [${stringifyForError(token)}]`); } -function assertHostNodeExists(rElement: RElement, elementOrSelector: RElement | string) { +function assertHostNodeExists(rElement: RElement, elementOrSelector: RElement|string) { if (!rElement) { if (typeof elementOrSelector === 'string') { 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. */ export function locateHostElement( - renderer: Renderer3, elementOrSelector: RElement | string, + renderer: Renderer3, elementOrSelector: RElement|string, encapsulation: ViewEncapsulation): RElement { if (isProceduralRenderer(renderer)) { // 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' ? - renderer.querySelector(elementOrSelector) ! : + renderer.querySelector(elementOrSelector)! : elementOrSelector; ngDevMode && assertHostNodeExists(rElement, elementOrSelector); @@ -780,7 +780,7 @@ export function storeCleanupFn(tView: TView, lView: LView, cleanupFn: Function): getLCleanup(lView).push(cleanupFn); 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 */ export function createTNode( - tView: TView, tParent: TElementNode | TContainerNode | null, type: TNodeType, - adjustedIndex: number, tagName: string | null, attrs: TAttributes | null): TNode { + tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, adjustedIndex: number, + tagName: string|null, attrs: TAttributes|null): TNode { ngDevMode && ngDevMode.tNode++; let injectorIndex = tParent ? tParent.injectorIndex : -1; return ngDevMode ? new TNodeDebug( @@ -866,7 +866,7 @@ export function createTNode( function generatePropertyAliases( inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number, - propStore: PropertyAliases | null): PropertyAliases|null { + propStore: PropertyAliases|null): PropertyAliases|null { for (let publicName in inputAliasMap) { if (inputAliasMap.hasOwnProperty(publicName)) { propStore = propStore === null ? {} : propStore; @@ -942,7 +942,7 @@ function mapPropName(name: string): string { export function elementPropertyInternal( 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.'); const element = getNativeByTNode(tNode, lView) as RElement | RComment; let inputData = tNode.inputs; @@ -994,7 +994,7 @@ function markDirtyIfOnPush(lView: LView, viewIndex: number): void { } 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]; attrName = normalizeDebugBindingName(attrName); const debugValue = normalizeDebugBindingValue(value); @@ -1018,7 +1018,7 @@ function setNgReflectProperty( } export function setNgReflectProperties( - lView: LView, element: RElement | RComment, type: TNodeType, dataValue: PropertyAliasValue, + lView: LView, element: RElement|RComment, type: TNodeType, dataValue: PropertyAliasValue, value: any) { if (type === TNodeType.Element || type === TNodeType.Container) { /** @@ -1036,7 +1036,7 @@ export function setNgReflectProperties( } function validateProperty( - tView: TView, lView: LView, element: RElement | RComment, propName: string, + tView: TView, lView: LView, element: RElement|RComment, propName: string, tNode: TNode): boolean { // 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). @@ -1050,7 +1050,7 @@ function validateProperty( 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; if (schemas !== null) { @@ -1099,8 +1099,8 @@ export function instantiateRootComponent(tView: TView, lView: LView, def: Com * Resolve the matched directives on a node. */ export function resolveDirectives( - tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode, - localRefs: string[] | null): boolean { + tView: TView, lView: LView, tNode: TElementNode|TContainerNode|TElementContainerNode, + localRefs: string[]|null): boolean { // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in // tsickle. ngDevMode && assertFirstCreatePass(tView); @@ -1108,7 +1108,7 @@ export function resolveDirectives( let hasDirectives = false; if (getBindingsEnabled()) { const directiveDefs: DirectiveDef[]|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) { let totalDirectiveHostVars = 0; @@ -1135,7 +1135,7 @@ export function resolveDirectives( 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.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0) @@ -1152,8 +1152,8 @@ export function resolveDirectives( } if (!preOrderCheckHooksFound && (def.onChanges || def.doCheck)) { - (tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [ - ])).push(tNode.index - HEADER_OFFSET); + (tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])) + .push(tNode.index - HEADER_OFFSET); preOrderCheckHooksFound = true; } @@ -1178,9 +1178,9 @@ export function resolveDirectives( * @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add. */ export function addHostBindingsToExpandoInstructions( - tView: TView, def: ComponentDef| DirectiveDef): void { + tView: TView, def: ComponentDef|DirectiveDef): void { 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 // suboptimal for performance. `def.hostBindings` may be null, // but we still need to push null to the array as a placeholder @@ -1244,7 +1244,7 @@ function instantiateAllDirectives( attachPatchData(directive, lView); if (initialInputs !== null) { - setInputsFromAttrs(lView, i - start, directive, def, tNode, initialInputs !); + setInputsFromAttrs(lView, i - start, directive, def, tNode, initialInputs!); } if (isComponent) { @@ -1257,7 +1257,7 @@ function instantiateAllDirectives( function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) { const start = tNode.directiveStart; const end = tNode.directiveEnd; - const expando = tView.expandoInstructions !; + const expando = tView.expandoInstructions!; const firstCreatePass = tView.firstCreatePass; const elementIndex = tNode.index - HEADER_OFFSET; try { @@ -1284,7 +1284,7 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode) */ export function invokeHostBindingsInCreationMode(def: DirectiveDef, directive: any) { if (def.hostBindings !== null) { - def.hostBindings !(RenderFlags.Create, directive); + def.hostBindings!(RenderFlags.Create, directive); } } @@ -1296,9 +1296,10 @@ export function invokeHostBindingsInCreationMode(def: DirectiveDef, directi */ export function generateExpandoInstructionBlock( tView: TView, tNode: TNode, directiveCount: number): void { - ngDevMode && assertEqual( - tView.firstCreatePass, true, - 'Expando block should only be generated on first create pass.'); + ngDevMode && + assertEqual( + 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 // 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 providerStartIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask; const providerCount = tView.data.length - providerStartIndex; - (tView.expandoInstructions || (tView.expandoInstructions = [ - ])).push(elementIndex, providerCount, directiveCount); + (tView.expandoInstructions || (tView.expandoInstructions = [])) + .push(elementIndex, providerCount, directiveCount); } /** -* 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. -*/ + * 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. + */ function findDirectiveDefMatches( tView: TView, viewData: LView, - tNode: TElementNode | TContainerNode | TElementContainerNode): DirectiveDef[]|null { + tNode: TElementNode|TContainerNode|TElementContainerNode): DirectiveDef[]|null { ngDevMode && assertFirstCreatePass(tView); - ngDevMode && assertNodeOfPossibleTypes( - tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container); + ngDevMode && + assertNodeOfPossibleTypes( + tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container); const registry = tView.directiveRegistry; let matches: any[]|null = null; if (registry) { for (let i = 0; i < registry.length; i++) { const def = registry[i] as ComponentDef| DirectiveDef; - if (isNodeMatchingSelectorList(tNode, def.selectors !, /* isProjectionMode */ false)) { + if (isNodeMatchingSelectorList(tNode, def.selectors!, /* isProjectionMode */ false)) { matches || (matches = ngDevMode ? new MatchesArray() : []); 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: * - setting appropriate TNode flags; * - 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 { ngDevMode && assertFirstCreatePass(tView); hostTNode.flags |= TNodeFlags.isComponentHost; - (tView.components || (tView.components = ngDevMode ? new TViewComponents() : [ - ])).push(hostTNode.index); + (tView.components || (tView.components = ngDevMode ? new TViewComponents() : [])) + .push(hostTNode.index); } /** Caches local names and their matching directive indices for query and template lookups. */ function cacheMatchingLocalNames( - tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void { + tNode: TNode, localRefs: string[]|null, exportsMap: {[key: string]: number}): void { if (localRefs) { - const localNames: (string | number)[] = tNode.localNames = - ngDevMode ? new TNodeLocalNames() : []; + const localNames: (string|number)[] = tNode.localNames = ngDevMode ? new TNodeLocalNames() : []; // 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 @@ -1375,12 +1376,12 @@ function cacheMatchingLocalNames( } /** -* Builds up an export map as directives are created, so local refs can be quickly mapped -* to their directive instances. -*/ + * Builds up an export map as directives are created, so local refs can be quickly mapped + * to their directive instances. + */ function saveNameToExportMap( - index: number, def: DirectiveDef| ComponentDef, - exportsMap: {[key: string]: number} | null) { + index: number, def: DirectiveDef|ComponentDef, + exportsMap: {[key: string]: number}|null) { if (exportsMap) { if (def.exportAs) { for (let i = 0; i < def.exportAs.length; i++) { @@ -1397,9 +1398,10 @@ function saveNameToExportMap( * @param index the initial index */ export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) { - ngDevMode && assertNotEqual( - numberOfDirectives, tNode.directiveEnd - tNode.directiveStart, - 'Reached the max number of directives'); + ngDevMode && + assertNotEqual( + numberOfDirectives, tNode.directiveEnd - tNode.directiveStart, + 'Reached the max number of directives'); tNode.flags |= TNodeFlags.isDirectiveHost; // When the first directive is created on a node, save the index tNode.directiveStart = index; @@ -1410,7 +1412,7 @@ export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: function baseResolveDirective(tView: TView, viewData: LView, def: DirectiveDef) { tView.data.push(def); 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); tView.blueprint.push(nodeInjectorFactory); viewData.push(nodeInjectorFactory); @@ -1435,8 +1437,8 @@ function addComponentLogic(lView: LView, hostTNode: TElementNode, def: Compon } export function elementAttributeInternal( - tNode: TNode, lView: LView, name: string, value: any, sanitizer: SanitizerFn | null | undefined, - namespace: string | null | undefined) { + tNode: TNode, lView: LView, name: string, value: any, sanitizer: SanitizerFn|null|undefined, + namespace: string|null|undefined) { ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); ngDevMode && validateAgainstEventAttributes(name); const element = getNativeByTNode(tNode, lView) as RElement; @@ -1472,7 +1474,7 @@ export function elementAttributeInternal( function setInputsFromAttrs( lView: LView, directiveIndex: number, instance: T, def: DirectiveDef, tNode: TNode, initialInputData: InitialInputData): void { - const initialInputs: InitialInputs|null = initialInputData ![directiveIndex]; + const initialInputs: InitialInputs|null = initialInputData![directiveIndex]; if (initialInputs !== null) { const setInput = def.setInput; for (let i = 0; i < initialInputs.length;) { @@ -1480,7 +1482,7 @@ function setInputsFromAttrs( const privateName = initialInputs[i++]; const value = initialInputs[i++]; if (setInput !== null) { - def.setInput !(instance, value, publicName, privateName); + def.setInput!(instance, value, publicName, privateName); } else { (instance as any)[privateName] = value; } @@ -1554,7 +1556,7 @@ const LContainerArray: any = ((typeof ngDevMode === 'undefined' || ngDevMode) && * @returns LContainer */ export function createLContainer( - hostNative: RElement | RComment | LView, currentView: LView, native: RComment, + hostNative: RElement|RComment|LView, currentView: LView, native: RComment, tNode: TNode): LContainer { ngDevMode && assertLView(currentView); ngDevMode && !isProceduralRenderer(currentView[RENDERER]) && assertDomNode(native); @@ -1569,7 +1571,7 @@ export function createLContainer( tNode, // t_host native, // native, null, // view refs - ); + ); ngDevMode && attachLContainerDebug(lContainer); return lContainer; } @@ -1594,14 +1596,14 @@ function refreshDynamicEmbeddedViews(lView: LView) { ngDevMode && assertDefined(embeddedTView, 'TView must be allocated'); if (viewAttachedToChangeDetector(embeddedLView)) { refreshView( - embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT] !); + embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!); } } if ((activeIndexFlag & ActiveIndexFlag.HAS_TRANSPLANTED_VIEWS) !== 0) { // 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 // 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]; @@ -1619,13 +1621,13 @@ function refreshDynamicEmbeddedViews(lView: LView) { * @param declaredComponentLView The `lContainer` parent component `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'); for (let i = 0; i < movedViews.length; i++) { - const movedLView = movedViews[i] !; + const movedLView = movedViews[i]!; const insertionLContainer = movedLView[PARENT] as LContainer; ngDevMode && assertLContainer(insertionLContainer); - const insertedComponentLView = insertionLContainer[PARENT][DECLARATION_COMPONENT_VIEW] !; + const insertedComponentLView = insertionLContainer[PARENT][DECLARATION_COMPONENT_VIEW]!; ngDevMode && assertDefined(insertedComponentLView, 'Missing LView'); // Check if we have a transplanted view by compering declaration and insertion location. if (insertedComponentLView !== declaredComponentLView) { @@ -1643,7 +1645,7 @@ function refreshTransplantedViews(lContainer: LContainer, declaredComponentLView // point. const movedTView = movedLView[TVIEW]; 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(lView: LView, lViewOrL // 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. if (lView[CHILD_HEAD]) { - lView[CHILD_TAIL] ![NEXT] = lViewOrLContainer; + lView[CHILD_TAIL]![NEXT] = lViewOrLContainer; } else { lView[CHILD_HEAD] = lViewOrLContainer; } @@ -1758,7 +1760,7 @@ export function markViewDirty(lView: LView): LView|null { return lView; } // continue otherwise - lView = parent !; + lView = parent!; } return null; } @@ -1797,7 +1799,7 @@ export function scheduleTick(rootContext: RootContext, flags: RootContextFlags) } rootContext.clean = _CLEAN_PROMISE; - res !(null); + res!(null); }); } } @@ -1805,7 +1807,7 @@ export function scheduleTick(rootContext: RootContext, flags: RootContextFlags) export function tickRootContext(rootContext: RootContext) { for (let i = 0; i < rootContext.components.length; i++) { const rootComponent = rootContext.components[i]; - const lView = readPatchedLView(rootComponent) !; + const lView = readPatchedLView(rootComponent)!; const tView = lView[TVIEW]; renderComponentOrTemplate(tView, lView, tView.template, rootComponent); } @@ -1929,7 +1931,7 @@ function getTViewCleanup(tView: TView): any[] { * instead of the current renderer (see the componentSyntheticHost* instructions). */ export function loadComponentRenderer(tNode: TNode, lView: LView): Renderer3 { - const componentLView = unwrapLView(lView[tNode.index]) !; + const componentLView = unwrapLView(lView[tNode.index])!; return componentLView[RENDERER]; } @@ -1958,7 +1960,7 @@ export function setInputsForProperty( ngDevMode && assertDataInRange(lView, index); const def = tView.data[index] as DirectiveDef; if (def.setInput !== null) { - def.setInput !(instance, value, publicName, privateName); + def.setInput!(instance, value, publicName, privateName); } else { instance[privateName] = value; } diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 9a7e4ee0f8..eb943a97ff 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -365,8 +365,10 @@ export const enum InitPhaseState { /** More flags associated with an LView (saved in LView[PREORDER_HOOK_FLAGS]) */ 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, /** @@ -617,7 +619,7 @@ export interface TView { * Even indices: Directive index * 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 @@ -684,7 +686,11 @@ export interface TView { 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; } +/** 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. * @@ -734,7 +749,27 @@ export interface RootContext { * Special cases: * - 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. @@ -764,8 +799,8 @@ export type HookData = (number | (() => void))[]; * Injector bloom filters are also stored here. */ export type TData = - (TNode | PipeDef| DirectiveDef| ComponentDef| number | TStylingRange | - TStylingKey | Type| InjectionToken| TI18n | I18nUpdateOpCodes | null | string)[]; + (TNode|PipeDef|DirectiveDef|ComponentDef|number|TStylingRange|TStylingKey| + Type|InjectionToken|TI18n|I18nUpdateOpCodes|null|string)[]; // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 50c33997e4..ce5315440c 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -10,6 +10,7 @@ import {Renderer2} from '../core'; import {ViewEncapsulation} from '../metadata/view'; import {addToArray, removeFromArray} from '../util/array_utils'; import {assertDefined, assertDomNode, assertEqual, assertString} from '../util/assert'; + import {assertLContainer, assertLView, assertTNodeForLView} from './assert'; import {attachPatchData} from './context_discovery'; 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 {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; 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 {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 {getLViewParent} from './util/view_traversal_utils'; import {getNativeByTNode, unwrapRNode} from './util/view_utils'; @@ -74,8 +75,8 @@ const enum WalkTNodeTreeAction { * being passed as an argument. */ function applyToElementOrContainer( - action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null, - lNodeToHandle: RNode | LContainer | LView, beforeNode?: RNode | null) { + action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement|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 // 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" @@ -91,7 +92,7 @@ function applyToElementOrContainer( } else if (isLView(lNodeToHandle)) { isComponent = true; ngDevMode && assertDefined(lNodeToHandle[HOST], 'HOST must be defined for a component LView'); - lNodeToHandle = lNodeToHandle[HOST] !; + lNodeToHandle = lNodeToHandle[HOST]!; } const rNode: RNode = unwrapRNode(lNodeToHandle); ngDevMode && !isProceduralRenderer(renderer) && assertDomNode(rNode); @@ -108,7 +109,7 @@ function applyToElementOrContainer( nativeRemoveNode(renderer, rNode, isComponent); } else if (action === WalkTNodeTreeAction.Destroy) { ngDevMode && ngDevMode.rendererDestroyNode++; - (renderer as ProceduralRenderer3).destroyNode !(rNode); + (renderer as ProceduralRenderer3).destroyNode!(rNode); } if (lContainer != null) { 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 */ 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( tView: TView, lView: LView, insertMode: false, beforeNode: null): void; 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); ngDevMode && assertNodeType(tView.node as TNode, TNodeType.View); if (renderParent) { @@ -196,13 +197,13 @@ export function destroyViewTree(rootView: LView): void { if (!next) { // Only clean up view when moving to the side or up, as destroy hooks // 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); lViewOrLContainer = getParentState(lViewOrLContainer, rootView); } if (lViewOrLContainer === null) lViewOrLContainer = rootView; isLView(lViewOrLContainer) && cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer); - next = lViewOrLContainer && lViewOrLContainer ![NEXT]; + next = lViewOrLContainer && lViewOrLContainer![NEXT]; } lViewOrLContainer = next; } @@ -267,7 +268,7 @@ function trackMovedView(declarationContainer: LContainer, lView: LView) { const movedViews = declarationContainer[MOVED_VIEWS]; const insertedLContainer = lView[PARENT] as LContainer; ngDevMode && assertLContainer(insertedLContainer); - const insertedComponentLView = insertedLContainer[PARENT] ![DECLARATION_COMPONENT_VIEW]; + const insertedComponentLView = insertedLContainer[PARENT]![DECLARATION_COMPONENT_VIEW]; ngDevMode && assertDefined(insertedComponentLView, 'Missing insertedComponentLView'); const insertedComponentIsOnPush = (insertedComponentLView[FLAGS] & LViewFlags.CheckAlways) !== LViewFlags.CheckAlways; @@ -291,10 +292,11 @@ function trackMovedView(declarationContainer: LContainer, lView: LView) { function detachMovedView(declarationContainer: LContainer, lView: LView) { ngDevMode && assertLContainer(declarationContainer); - ngDevMode && assertDefined( - declarationContainer[MOVED_VIEWS], - 'A projected view should belong to a non-empty projected views collection'); - const movedViews = declarationContainer[MOVED_VIEWS] !; + ngDevMode && + assertDefined( + 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); 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 * @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 { let tNode; 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 { const tCleanup = tView.cleanup; if (tCleanup !== null) { - const lCleanup = lView[CLEANUP] !; + const lCleanup = lView[CLEANUP]!; for (let i = 0; i < tCleanup.length - 1; i += 2) { if (typeof tCleanup[i] === 'string') { // This is a native DOM listener @@ -484,7 +486,7 @@ function removeListeners(tView: TView, lView: LView): void { /** Calls onDestroy hooks for this view */ function executeOnDestroys(tView: TView, lView: LView): void { - let destroyHooks: HookData|null; + let destroyHooks: DestroyHookData|null; if (tView != null && (destroyHooks = tView.destroyHooks) != null) { 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. 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 // can't be used as a render parent. let parentTNode = tNode.parent; - while (parentTNode != null && (parentTNode.type === TNodeType.ElementContainer || - parentTNode.type === TNodeType.IcuContainer)) { + while (parentTNode != null && + (parentTNode.type === TNodeType.ElementContainer || + parentTNode.type === TNodeType.IcuContainer)) { tNode = parentTNode; 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 // or a component view. if (parentTNode == null) { - const hostTNode = currentView[T_HOST] !; + const hostTNode = currentView[T_HOST]!; if (hostTNode.type === TNodeType.View) { // 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: @@ -575,7 +586,7 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme * actual renderer being used. */ 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++; if (isProceduralRenderer(renderer)) { renderer.insertBefore(parent, child, beforeNode); @@ -595,7 +606,7 @@ function nativeAppendChild(renderer: Renderer3, parent: RElement, child: RNode): } function nativeAppendOrInsertBefore( - renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode | null) { + renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode|null) { if (beforeNode !== null) { nativeInsertBefore(renderer, parent, child, beforeNode); } else { @@ -659,11 +670,11 @@ function getNativeAnchorNode(parentTNode: TNode, lView: LView): RNode|null { * @returns Whether or not the child was appended */ 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); if (renderParent != null) { 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); if (Array.isArray(childEl)) { 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). */ -function getFirstNativeNode(lView: LView, tNode: TNode | null): RNode|null { +function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null { if (tNode !== null) { - ngDevMode && assertNodeOfPossibleTypes( - tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer, - TNodeType.IcuContainer, TNodeType.Projection); + ngDevMode && + assertNodeOfPossibleTypes( + tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer, + TNodeType.IcuContainer, TNodeType.Projection); const tNodeType = tNode.type; 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 parentView = getLViewParent(componentView); 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) { - return getFirstNativeNode(parentView !, firstProjectedTNode); + return getFirstNativeNode(parentView!, firstProjectedTNode); } else { return getFirstNativeNode(lView, tNode.next); } @@ -757,13 +769,14 @@ export function nativeRemoveNode(renderer: Renderer3, rNode: RNode, isHostElemen * nodes on the LView or projection boundary. */ function applyNodes( - renderer: Renderer3, action: WalkTNodeTreeAction, tNode: TNode | null, lView: LView, - renderParent: RElement | null, beforeNode: RNode | null, isProjection: boolean) { + renderer: Renderer3, action: WalkTNodeTreeAction, tNode: TNode|null, lView: LView, + renderParent: RElement|null, beforeNode: RNode|null, isProjection: boolean) { while (tNode != null) { ngDevMode && assertTNodeForLView(tNode, lView); - ngDevMode && assertNodeOfPossibleTypes( - tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer, - TNodeType.Projection, TNodeType.Projection, TNodeType.IcuContainer); + ngDevMode && + assertNodeOfPossibleTypes( + tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer, + TNodeType.Projection, TNodeType.Projection, TNodeType.IcuContainer); const rawSlotValue = lView[tNode.index]; const tNodeType = tNode.type; if (isProjection) { @@ -814,9 +827,9 @@ function applyNodes( */ function applyView( tView: TView, lView: LView, renderer: Renderer3, action: WalkTNodeTreeAction, - renderParent: RElement | null, beforeNode: RNode | null) { - ngDevMode && assertNodeType(tView.node !, TNodeType.View); - const viewRootTNode: TNode|null = tView.node !.child; + renderParent: RElement|null, beforeNode: RNode|null) { + ngDevMode && assertNodeType(tView.node!, TNodeType.View); + const viewRootTNode: TNode|null = tView.node!.child; applyNodes(renderer, action, viewRootTNode, lView, renderParent, beforeNode, false); } @@ -833,7 +846,7 @@ function applyView( export function applyProjection(tView: TView, lView: LView, tProjectionNode: TProjectionNode) { const renderer = lView[RENDERER]; 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); applyProjectionRecursive( renderer, WalkTNodeTreeAction.Create, lView, tProjectionNode, renderParent, beforeNode); @@ -855,12 +868,12 @@ export function applyProjection(tView: TView, lView: LView, tProjectionNode: TPr */ function applyProjectionRecursive( 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 componentNode = componentLView[T_HOST] as TElementNode; ngDevMode && assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index'); - const nodeToProjectOrRNodes = componentNode.projection ![tProjectionNode.projection] !; + const nodeToProjectOrRNodes = componentNode.projection![tProjectionNode.projection]!; if (Array.isArray(nodeToProjectOrRNodes)) { // 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 @@ -895,7 +908,7 @@ function applyProjectionRecursive( */ function applyContainer( renderer: Renderer3, action: WalkTNodeTreeAction, lContainer: LContainer, - renderParent: RElement | null, beforeNode: RNode | null | undefined) { + renderParent: RElement|null, beforeNode: RNode|null|undefined) { ngDevMode && assertLContainer(lContainer); const anchor = lContainer[NATIVE]; // LContainer has its own before node. const native = unwrapRNode(lContainer); @@ -1016,4 +1029,4 @@ export function writeDirectClass(renderer: Renderer3, element: RElement, newValu element.className = newValue; } ngDevMode && ngDevMode.rendererSetClassName++; -} \ No newline at end of file +} diff --git a/packages/core/test/acceptance/providers_spec.ts b/packages/core/test/acceptance/providers_spec.ts index 0c46b74098..90a40decf6 100644 --- a/packages/core/test/acceptance/providers_spec.ts +++ b/packages/core/test/acceptance/providers_spec.ts @@ -6,16 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, Inject, Injectable, InjectionToken, Injector, NgModule, Optional, forwardRef} from '@angular/core'; -import {TestBed, async, inject} from '@angular/core/testing'; +import {Component, Directive, forwardRef, Inject, Injectable, InjectionToken, Injector, NgModule, Optional} from '@angular/core'; +import {async, inject, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; 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('inheritance', () => { - it('should NOT inherit providers', () => { const SOME_DIRS = new InjectionToken('someDirs'); @@ -52,7 +50,6 @@ describe('providers', () => { expect(otherDir.dirs.length).toEqual(1); expect(otherDir.dirs[0] instanceof SubDirective).toBe(true); }); - }); describe('lifecycles', () => { @@ -61,7 +58,9 @@ describe('providers', () => { @Injectable() class SuperInjectableWithDestroyHook { - ngOnDestroy() { logs.push('OnDestroy'); } + ngOnDestroy() { + logs.push('OnDestroy'); + } } @Injectable() @@ -86,7 +85,9 @@ describe('providers', () => { @Injectable() class InjectableWithDestroyHook { - ngOnDestroy() { logs.push('OnDestroy'); } + ngOnDestroy() { + logs.push('OnDestroy'); + } } @Component({template: '', providers: [InjectableWithDestroyHook]}) @@ -106,7 +107,9 @@ describe('providers', () => { @Injectable() class InjectableWithDestroyHook { - ngOnDestroy() { logs.push('OnDestroy'); } + ngOnDestroy() { + logs.push('OnDestroy'); + } } @Component({selector: 'my-cmp', template: ''}) @@ -137,7 +140,9 @@ describe('providers', () => { @Injectable() class InjectableWithDestroyHook { - ngOnDestroy() { logs.push('OnDestroy'); } + ngOnDestroy() { + logs.push('OnDestroy'); + } } @Component({ @@ -162,12 +167,16 @@ describe('providers', () => { @Injectable() class InjectableWithDestroyHookToken { - ngOnDestroy() { logs.push('OnDestroy Token'); } + ngOnDestroy() { + logs.push('OnDestroy Token'); + } } @Injectable() class InjectableWithDestroyHookValue { - ngOnDestroy() { logs.push('OnDestroy Value'); } + ngOnDestroy() { + logs.push('OnDestroy Value'); + } } @Component({ @@ -193,21 +202,23 @@ describe('providers', () => { @Injectable() class InjectableWithDestroyHookToken { - ngOnDestroy() { logs.push('OnDestroy Token'); } + ngOnDestroy() { + logs.push('OnDestroy Token'); + } } @Injectable() class InjectableWithDestroyHookExisting { - ngOnDestroy() { logs.push('OnDestroy Existing'); } + ngOnDestroy() { + logs.push('OnDestroy Existing'); + } } @Component({ template: '', providers: [ - InjectableWithDestroyHookExisting, { - provide: InjectableWithDestroyHookToken, - useExisting: InjectableWithDestroyHookExisting - } + InjectableWithDestroyHookExisting, + {provide: InjectableWithDestroyHookToken, useExisting: InjectableWithDestroyHookExisting} ] }) 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', () => { - const resolvedServices: (DestroyService | undefined)[] = []; - const destroyContexts: (DestroyService | undefined)[] = []; + const resolvedServices: (DestroyService|undefined)[] = []; + const destroyContexts: (DestroyService|undefined)[] = []; let parentService: DestroyService|undefined; let childService: DestroyService|undefined; @Injectable() class DestroyService { - constructor() { resolvedServices.push(this); } - ngOnDestroy() { destroyContexts.push(this); } + constructor() { + resolvedServices.push(this); + } + ngOnDestroy() { + destroyContexts.push(this); + } } @Directive({selector: '[dir-one]', providers: [DestroyService]}) class DirOne { - constructor(service: DestroyService) { childService = service; } + constructor(service: DestroyService) { + childService = service; + } } @Directive({selector: '[dir-two]', providers: [DestroyService]}) class DirTwo { - constructor(service: DestroyService) { childService = service; } + constructor(service: DestroyService) { + childService = service; + } } @Component({template: '
', providers: [DestroyService]}) class App { - constructor(service: DestroyService) { parentService = service; } + constructor(service: DestroyService) { + parentService = service; + } } TestBed.configureTestingModule({declarations: [App, DirOne, DirTwo]}); @@ -266,28 +287,36 @@ describe('providers', () => { 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', () => { - const resolvedServices: (DestroyService | undefined)[] = []; - const destroyContexts: (DestroyService | undefined)[] = []; + const resolvedServices: (DestroyService|undefined)[] = []; + const destroyContexts: (DestroyService|undefined)[] = []; const token = new InjectionToken('token'); let parentService: DestroyService|undefined; let childService: DestroyService|undefined; @Injectable() class DestroyService { - constructor() { resolvedServices.push(this); } - ngOnDestroy() { destroyContexts.push(this); } + constructor() { + resolvedServices.push(this); + } + ngOnDestroy() { + destroyContexts.push(this); + } } @Directive( {selector: '[dir-one]', providers: [{provide: token, useClass: DestroyService}]}) class DirOne { - constructor(@Inject(token) service: DestroyService) { childService = service; } + constructor(@Inject(token) service: DestroyService) { + childService = service; + } } @Directive( {selector: '[dir-two]', providers: [{provide: token, useClass: DestroyService}]}) class DirTwo { - constructor(@Inject(token) service: DestroyService) { childService = service; } + constructor(@Inject(token) service: DestroyService) { + childService = service; + } } @Component({ @@ -295,7 +324,9 @@ describe('providers', () => { providers: [{provide: token, useClass: DestroyService}] }) class App { - constructor(@Inject(token) service: DestroyService) { parentService = service; } + constructor(@Inject(token) service: DestroyService) { + parentService = service; + } } TestBed.configureTestingModule({declarations: [App, DirOne, DirTwo]}); @@ -310,21 +341,151 @@ describe('providers', () => { expect(destroyContexts).toEqual([parentService, childService]); }); + 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('SERVICES'); + + @Injectable() + class DestroyService { + ngOnDestroy() { + destroyCalls.push(this); + } + } + + @Injectable() + class OtherDestroyService { + ngOnDestroy() { + destroyCalls.push(this); + } + } + + @Component({ + template: '
', + 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('SERVICES'); + + @Injectable() + class Service1 { + } + + @Injectable() + class Service2 { + ngOnDestroy() { + destroyCalls.push(this); + } + } + + @Injectable() + class Service3 { + } + + @Injectable() + class Service4 { + ngOnDestroy() { + destroyCalls.push(this); + } + } + + @Component({ + template: '
', + 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('SERVICES'); + + @Injectable() + class DestroyService { + ngOnDestroy() { + destroyCalls++; + } + } + + @Injectable() + class OtherDestroyService { + ngOnDestroy() { + destroyCalls++; + } + } + + @Component({ + template: '
', + 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', () => { - // 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; const SERVICES = new InjectionToken('SERVICES'); @Injectable() class DestroyService { - ngOnDestroy() { destroyCalls++; } + ngOnDestroy() { + destroyCalls++; + } } @Injectable() class OtherDestroyService { - ngOnDestroy() { destroyCalls++; } + ngOnDestroy() { + destroyCalls++; + } } @Component({ @@ -343,13 +504,11 @@ describe('providers', () => { fixture.detectChanges(); fixture.destroy(); - expect(destroyCalls).toBe(2); + expect(destroyCalls).toBe(0); }); - }); describe('components and directives', () => { - class MyService { value = 'some value'; } @@ -409,7 +568,6 @@ describe('providers', () => { }); describe('forward refs', () => { - it('should support forward refs in provider deps', () => { class MyService { constructor(public dep: {value: string}) {} @@ -444,7 +602,6 @@ describe('providers', () => { }); it('should support forward refs in useClass when impl version is also provided', () => { - @Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)}) abstract class SomeProvider { } @@ -490,11 +647,9 @@ describe('providers', () => { expect(fixture.componentInstance.foo).toBeAnInstanceOf(SomeProviderImpl); }); - }); describe('flags', () => { - class MyService { constructor(public value: OtherService|null) {} }