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": {
"uncompressed": {
"runtime-es2015": 2987,
"main-es2015": 451469,
"main-es2015": 452060,
"polyfills-es2015": 52195
}
}

View File

@ -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
}
}

View File

@ -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<Provider, any[]>, contextIndex: number) {
tView: TView, provider: Exclude<Provider, any[]>, contextIndex: number,
indexInFactory?: number) {
const providerIsTypeProvider = isTypeProvider(provider);
if (providerIsTypeProvider || isClassProvider(provider)) {
const prototype = ((provider as ClassProvider).useClass || provider).prototype;
const ngOnDestroy = prototype.ngOnDestroy;
if (ngOnDestroy) {
(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;

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 {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<string|null, Array<any>>;
let LVIEW_EMBEDDED_CACHE !: Map<string|null, Array<any>>;
let LVIEW_ROOT !: Array<any>;
let LVIEW_COMPONENT_CACHE!: Map<string|null, Array<any>>;
let LVIEW_EMBEDDED_CACHE!: Map<string|null, Array<any>>;
let LVIEW_ROOT!: Array<any>;
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<any> {
function getLViewToClone(type: TViewType, name: string|null): Array<any> {
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<any> {
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<any>|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 += `<EFBFBD>${-opCode - 1}<EFBFBD>`;
value += `<EFBFBD>${- opCode - 1}<EFBFBD>`;
} 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[];
}

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 {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<T>(
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<T>(
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<T>(
* @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<T>(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<T>(tView: TView, lView: LView, context: T): void {
* - refreshing child (embedded and component) views.
*/
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');
const flags = lView[FLAGS];
if ((flags & LViewFlags.Destroyed) === LViewFlags.Destroyed) return;
@ -505,7 +505,7 @@ export function refreshView<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 normalExecutionPath = !getCheckNoChangesMode();
const creationModeIsActive = isCreationMode(lView);
@ -618,10 +618,10 @@ export function getOrCreateTComponentView(def: ComponentDef<any>): TView {
* @param consts Constants for this view
*/
export function createTView(
type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>| null, decls: number,
vars: number, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null,
consts: TConstants | null): TView {
type: TViewType, viewIndex: number, templateFn: ComponentTemplate<any>|null, decls: number,
vars: number, directives: DirectiveDefListOrFactory|null, pipes: PipeDefListOrFactory|null,
viewQuery: ViewQueriesFunction<any>|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<T>(
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<T>(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<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) {
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<any>| DirectiveDef<any>): void {
tView: TView, def: ComponentDef<any>|DirectiveDef<any>): 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<any>, 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<any>, 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<any>[]|null {
tNode: TElementNode|TContainerNode|TElementContainerNode): DirectiveDef<any>[]|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<any>| DirectiveDef<any>;
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<any>| ComponentDef<any>,
exportsMap: {[key: string]: number} | null) {
index: number, def: DirectiveDef<any>|ComponentDef<any>,
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<T>(tView: TView, viewData: LView, def: DirectiveDef<T>) {
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<T>(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<T>(
lView: LView, directiveIndex: number, instance: T, def: DirectiveDef<T>, 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<T>(
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<T extends LView|LContainer>(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<any>;
if (def.setInput !== null) {
def.setInput !(instance, value, publicName, privateName);
def.setInput!(instance, value, publicName, privateName);
} else {
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]) */
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<any>| DirectiveDef<any>| ComponentDef<any>| number | TStylingRange |
TStylingKey | Type<any>| InjectionToken<any>| TI18n | I18nUpdateOpCodes | null | string)[];
(TNode|PipeDef<any>|DirectiveDef<any>|ComponentDef<any>|number|TStylingRange|TStylingKey|
Type<any>|InjectionToken<any>|TI18n|I18nUpdateOpCodes|null|string)[];
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.

View File

@ -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++;
}
}

View File

@ -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: '<div dir-one dir-two></div>', 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<any>('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<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', () => {
// 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<any>('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) {}
}