diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index f9e18c739e..24557d492f 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -171,7 +171,8 @@ export function renderComponent( let component: T; const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag); const oldView = enterView( - createLView(-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), []), + createLView( + -1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), {data: []}), null !); try { // Create element node at index 0 in data array diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 759dbef3d2..db626027df 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -207,11 +207,11 @@ export function getOrCreateInjectable(di: LInjector, token: Type, flags?: // nothing to the "left" of it so it doesn't need a mask. const start = flags >> LNodeFlags.INDX_SHIFT; - const ngStaticData = node.view.ngStaticData; + const tData = node.view.tView.data; for (let i = start, ii = start + size; i < ii; i++) { // Get the definition for the directive at this index and, if it is injectable (diPublic), // and matches the given token, return the directive instance. - const directiveDef = ngStaticData[i] as TypedDirectiveDef; + const directiveDef = tData[i] as TypedDirectiveDef; if (directiveDef.diPublic && directiveDef.type == token) { return node.view.data[i]; } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 925976ba02..976e1c8ee1 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -14,13 +14,13 @@ import {ViewContainerRef} from '../linker/view_container_ref'; import {Type} from '../type'; import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert'; -import {LContainer} from './interfaces/container'; +import {LContainer, TContainer} from './interfaces/container'; import {LInjector} from './interfaces/injector'; import {CssSelector, LProjection} from './interfaces/projection'; import {LQuery, QueryReadType} from './interfaces/query'; -import {LView} from './interfaces/view'; +import {LView, TData, TView} from './interfaces/view'; -import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, NgStaticData, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation'; import {isNodeMatchingSelector} from './node_selector_matcher'; @@ -82,19 +82,19 @@ let previousOrParentNode: LNode; let isParent: boolean; /** - * The current template's static data (shared between all templates of a - * given type). + * Static data that corresponds to the instance-specific data array on an LView. * - * Each node's static data is stored at the same index that it's stored - * in the data array. Any nodes that do not have static data store a null - * value to avoid a sparse array. + * Each node's static data is stored in tData at the same index that it's stored + * in the data array. Each directive's definition is stored here at the same index + * as its directive instance in the data array. Any nodes that do not have static + * data store a null value in tData to avoid a sparse array. */ -let ngStaticData: NgStaticData; +let tData: TData; /** State of the current view being processed. */ let currentView: LView; // The initialization has to be after the `let`, otherwise `createLView` can't see `let`. -currentView = createLView(null !, null !, []); +currentView = createLView(null !, null !, {data: []}); let currentQuery: LQuery|null; @@ -151,7 +151,7 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null) const oldView = currentView; data = newView.data; bindingIndex = newView.bindingStartIndex || 0; - ngStaticData = newView.ngStaticData; + tData = newView.tView.data; creationMode = newView.creationMode; viewHookStartIndex = newView.viewHookStartIndex; @@ -176,14 +176,13 @@ export function leaveView(newView: LView): void { enterView(newView, null); } -export function createLView( - viewId: number, renderer: Renderer3, ngStaticData: NgStaticData): LView { +export function createLView(viewId: number, renderer: Renderer3, tView: TView): LView { const newView = { parent: currentView, id: viewId, // -1 for component views node: null !, // until we initialize it in createNode. data: [], - ngStaticData: ngStaticData, + tView: tView, cleanup: null, renderer: renderer, child: null, @@ -246,10 +245,10 @@ export function createLNode( data[index] = node; // Every node adds a value to the static data array to avoid a sparse array - if (index >= ngStaticData.length) { - ngStaticData[index] = null; + if (index >= tData.length) { + tData[index] = null; } else { - node.tNode = ngStaticData[index] as TNode; + node.tNode = tData[index] as TNode; } // Now link ourselves into the tree. @@ -300,24 +299,23 @@ export function renderTemplate( rendererFactory = providedRendererFactory; host = createLNode( null, LNodeFlags.Element, hostNode, - createLView(-1, providedRendererFactory.createRenderer(null, null), [])); + createLView( + -1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template))); } const hostView = host.data !; ngDevMode && assertNotEqual(hostView, null, 'hostView'); - hostView.ngStaticData = getTemplateStatic(template); renderComponentOrTemplate(host, hostView, context, template); return host; } export function renderComponentOrTemplate( - node: LElementNode, lView: LView, componentOrContext: T, template?: ComponentTemplate) { - const oldView = enterView(lView, node); + node: LElementNode, hostView: LView, componentOrContext: T, template?: ComponentTemplate) { + const oldView = enterView(hostView, node); try { if (rendererFactory.begin) { rendererFactory.begin(); } if (template) { - ngStaticData = template.ngStaticData || (template.ngStaticData = [] as never); template(componentOrContext !, creationMode); } else { // Element was stored at 0 and directive was stored at 1 in renderComponent @@ -328,7 +326,7 @@ export function renderComponentOrTemplate( if (rendererFactory.end) { rendererFactory.end(); } - lView.creationMode = false; + hostView.creationMode = false; leaveView(oldView); } } @@ -450,10 +448,9 @@ export function elementStart( let componentView: LView|null = null; if (isHostElement) { - const ngStaticData = getTemplateStatic(hostComponentDef !.template); + const tView = getOrCreateTView(hostComponentDef !.template); componentView = addToViewTree(createLView( - -1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), - ngStaticData)); + -1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView)); } // Only component views should be added to the view tree directly. Embedded views are @@ -465,7 +462,7 @@ export function elementStart( if (node.tNode == null) { ngDevMode && assertDataInRange(index - 1); - node.tNode = ngStaticData[index] = + node.tNode = tData[index] = createTNode(name, attrs || null, null, hostComponentDef ? null : queryName); } @@ -532,14 +529,14 @@ function hack_findQueryName( } /** - * Gets static data from a template function or creates a new static - * data array if it doesn't already exist. + * Gets TView from a template function or creates a new TView + * if it doesn't already exist. * * @param template The template from which to get static data - * @returns NgStaticData + * @returns TView */ -function getTemplateStatic(template: ComponentTemplate): NgStaticData { - return template.ngStaticData || (template.ngStaticData = [] as never); +function getOrCreateTView(template: ComponentTemplate): TView { + return template.ngPrivateData || (template.ngPrivateData = { data: [] } as never); } function setUpAttributes(native: RElement, attrs: string[]): void { @@ -583,14 +580,15 @@ export function locateHostElement( } /** - * Creates the host LNode.. + * Creates the host LNode. * * @param rNode Render host element. + * @param def ComponentDef */ export function hostElement(rNode: RElement | null, def: ComponentDef) { resetApplicationState(); createLNode( - 0, LNodeFlags.Element, rNode, createLView(-1, renderer, getTemplateStatic(def.template))); + 0, LNodeFlags.Element, rNode, createLView(-1, renderer, getOrCreateTView(def.template))); } @@ -727,11 +725,11 @@ export function elementProperty(index: number, propName: string, value: T | N * * @param tagName * @param attrs - * @param containerStatic + * @param data * @returns the TNode object */ function createTNode( - tagName: string | null, attrs: string[] | null, containerStatic: (TNode | null)[][] | null, + tagName: string | null, attrs: string[] | null, data: TContainer | null, localName: string | null): TNode { return { tagName: tagName, @@ -740,7 +738,7 @@ function createTNode( initialInputs: undefined, inputs: undefined, outputs: undefined, - containerStatic: containerStatic + data: data }; } @@ -757,17 +755,17 @@ function setInputsForProperty(inputs: (number | string)[], value: any): void { /** * This function consolidates all the inputs or outputs defined by directives - * on this node into one object and stores it in ngStaticData so it can + * on this node into one object and stores it in tData so it can * be shared between all templates of this type. * - * @param index Index where data should be stored in ngStaticData + * @param index Index where data should be stored in tData */ function generatePropertyAliases(flags: number, tNode: TNode, isInputData = false): TNode { const start = flags >> LNodeFlags.INDX_SHIFT; const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT; for (let i = start, ii = start + size; i < ii; i++) { - const directiveDef: DirectiveDef = ngStaticData ![i] as DirectiveDef; + const directiveDef: DirectiveDef = tData ![i] as DirectiveDef; const propertyAliasMap: {[publicName: string]: string} = isInputData ? directiveDef.inputs : directiveDef.outputs; for (let publicName in propertyAliasMap) { @@ -946,12 +944,12 @@ export function directiveCreate( data[index] = instance = directive; - if (index >= ngStaticData.length) { - ngStaticData[index] = directiveDef !; + if (index >= tData.length) { + tData[index] = directiveDef !; if (queryName) { - ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.staticData'); - const nodeStaticData = previousOrParentNode !.tNode !; - (nodeStaticData.localNames || (nodeStaticData.localNames = [])).push(queryName, index); + ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode'); + const tNode = previousOrParentNode !.tNode !; + (tNode.localNames || (tNode.localNames = [])).push(queryName, index); } } @@ -960,9 +958,9 @@ export function directiveCreate( diPublic(directiveDef !); } - const staticData: TNode|null = previousOrParentNode.tNode !; - if (staticData && staticData.attrs) { - setInputsFromAttrs(instance, directiveDef !.inputs, staticData); + const tNode: TNode|null = previousOrParentNode.tNode !; + if (tNode && tNode.attrs) { + setInputsFromAttrs(instance, directiveDef !.inputs, tNode); } return instance; } @@ -1140,8 +1138,7 @@ export function container( if (node.tNode == null) { // TODO(misko): implement queryName caching const queryName: string|null = hack_findQueryName(null, localRefs, ''); - node.tNode = ngStaticData[index] = - createTNode(tagName || null, attrs || null, [], queryName || null); + node.tNode = tData[index] = createTNode(tagName || null, attrs || null, [], queryName || null); } // Containers are added to the current view tree instead of their embedded views @@ -1215,7 +1212,8 @@ export function viewStart(viewBlockId: number): boolean { enterView((existingView as LViewNode).data, previousOrParentNode as LViewNode); } else { // When we create a new LView, we always reset the state of the instructions. - const newView = createLView(viewBlockId, renderer, initViewStaticData(viewBlockId, container)); + const newView = + createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container)); enterView(newView, createLNode(null, LNodeFlags.View, null, newView)); lContainer.nextIndex++; } @@ -1224,24 +1222,24 @@ export function viewStart(viewBlockId: number): boolean { } /** - * Initialize the static data for the active view. + * Initialize the TView (e.g. static data) for the active embedded view. * - * Each embedded view needs to set the global ngStaticData variable to the static data for + * Each embedded view needs to set the global tData variable to the static data for * that view. Otherwise, the view's static data for a particular node would overwrite - * the staticdata for a node in the view above it with the same index (since it's in the + * the static data for a node in the view above it with the same index (since it's in the * same template). * - * @param viewIndex The index of the view's static data in containerStatic + * @param viewIndex The index of the TView in TContainer * @param parent The parent container in which to look for the view's static data - * @returns NgStaticData + * @returns TView */ -function initViewStaticData(viewIndex: number, parent: LContainerNode): NgStaticData { +function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TView { ngDevMode && assertNodeType(parent, LNodeFlags.Container); - const containerStatic = (parent !.tNode as TContainerNode).containerStatic; - if (viewIndex >= containerStatic.length || containerStatic[viewIndex] == null) { - containerStatic[viewIndex] = []; + const tContainer = (parent !.tNode as TContainerNode).data; + if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) { + tContainer[viewIndex] = { data: [] } as TView; } - return containerStatic[viewIndex]; + return tContainer[viewIndex]; } /** Marks the end of the LViewNode. */ @@ -1900,8 +1898,8 @@ function valueInData(data: any[], index: number, value?: T): T { } else { // We don't store any static data for local variables, so the first time // we see the template, we should store as null to avoid a sparse array - if (index >= ngStaticData.length) { - ngStaticData[index] = null; + if (index >= tData.length) { + tData[index] = null; } data[index] = value; } diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 0270133b80..956e7f472e 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -8,7 +8,7 @@ import {ComponentTemplate} from './definition'; import {LElementNode, LViewNode} from './node'; -import {LView} from './view'; +import {LView, TView} from './view'; /** The state associated with an LContainer */ @@ -68,6 +68,24 @@ export interface LContainer { readonly template: ComponentTemplate|null; } +/** + * The static equivalent of LContainer, used in TContainerNode. + * + * The container needs to store static data for each of its embedded views + * (TViews). Otherwise, nodes in embedded views with the same index as nodes + * in their parent views will overwrite each other, as they are in + * the same template. + * + * Each index in this array corresponds to the static data for a certain + * view. So if you had V(0) and V(1) in a container, you might have: + * + * [ + * [{tagName: 'div', attrs: ...}, null], // V(0) TView + * [{tagName: 'button', attrs ...}, null] // V(1) TView + * ] + */ +export type TContainer = TView[]; + // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index cd5cff3805..f6c3359b0b 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -14,7 +14,7 @@ import {resolveRendererType2} from '../../view/util'; * Definition of what a template rendering function should look like. */ export type ComponentTemplate = { - (ctx: T, creationMode: boolean): void; ngStaticData?: never; + (ctx: T, creationMode: boolean): void; ngPrivateData?: never; }; export type EmbeddedTemplate = (ctx: T) => void; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 27fe3a917e..8f188738f0 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -6,13 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {LContainer} from './container'; +import {LContainer, TContainer} from './container'; import {DirectiveDef} from './definition'; import {LInjector} from './injector'; import {LProjection} from './projection'; import {LQuery} from './query'; import {RComment, RElement, RText} from './renderer'; -import {LView} from './view'; +import {LView, TData, TView} from './view'; /** @@ -198,10 +198,6 @@ export interface LProjectionNode extends LNode { readonly parent: LElementNode|LViewNode; } - -/** The type of the global ngStaticData array. */ -export type NgStaticData = (TNode | DirectiveDef| null)[]; - /** * LNode binding data (flyweight) for a particular node that is shared between all templates * of a specific type. @@ -263,28 +259,21 @@ export interface TNode { outputs: PropertyAliases|null|undefined; /** + * The static data equivalent of LNode.data. + * * If this TNode corresponds to an LContainerNode, the container will - * need to have nested static data for each of its embedded views. - * Otherwise, nodes in embedded views with the same index as nodes - * in their parent views will overwrite each other, as they are in - * the same template. + * need to store separate static data for each of its views (TContainer). * - * Each index in this array corresponds to the static data for a certain - * view. So if you had V(0) and V(1) in a container, you might have: - * - * [ - * [{tagName: 'div', attrs: ...}, null], // V(0) ngData - * [{tagName: 'button', attrs ...}, null] // V(1) ngData - * ] + * If this TNode corresponds to an LElementNode, data will be null. */ - containerStatic: (TNode|null)[][]|null; + data: TContainer|null; } -/** Static data for an LElementNode */ -export interface TElementNode extends TNode { containerStatic: null; } +/** Static data for an LElementNode */ +export interface TElementNode extends TNode { data: null; } /** Static data for an LContainerNode */ -export interface TContainerNode extends TNode { containerStatic: (TNode|null)[][]; } +export interface TContainerNode extends TNode { data: TContainer; } /** * This mapping is necessary so we can set input properties and output listeners diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 72652c8e77..6db78fcd02 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -129,19 +129,15 @@ export interface LView { * appear in the template, starting with `bindingStartIndex`. * We use `bindingIndex` to internally keep track of which binding * is currently active. - * - * NOTE: We also use data == null as a marker for creationMode. We - * do this by creating ViewState in incomplete state with nodes == null - * and we initialize it on first run. */ readonly data: any[]; /** - * The static data array for the current view. We need a reference to this so we - * can easily walk up the node tree in DI and get the ngStaticData array associated - * with a node (where the directive defs are stored). + * The static data for this view. We need a reference to this so we can easily walk up the + * node tree in DI and get the TView.data array associated with a node (where the + * directive defs are stored). */ - ngStaticData: (TNode|DirectiveDef|null)[]; + tView: TView; } /** Interface necessary to work with view tree traversal */ @@ -152,6 +148,24 @@ export interface LViewOrLContainer { parent: LView|null; } +/** + * The static data for an LView (shared between all templates of a + * given type). + * + * Stored on the template function as ngPrivateData. + */ +export interface TView { data: TData; } + +/** + * Static data that corresponds to the instance-specific data array on an LView. + * + * Each node's static data is stored in tData at the same index that it's stored + * in the data array. Each directive's definition is stored here at the same index + * as its directive instance in the data array. Any nodes that do not have static + * data store a null value in tData to avoid a sparse array. + */ +export type TData = (TNode | DirectiveDef| null)[]; + // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 44f3c71891..4a3196a9bb 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -139,12 +139,12 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { * @returns Index of a found directive or null when none found. */ function geIdxOfMatchingDirective(node: LNode, type: Type): number|null { - const ngStaticData = node.view.ngStaticData; + const tData = node.view.tView.data; const flags = node.flags; for (let i = flags >> LNodeFlags.INDX_SHIFT, ii = i + ((flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT); i < ii; i++) { - const def = ngStaticData[i] as TypedDirectiveDef; + const def = tData[i] as TypedDirectiveDef; if (def.diPublic && def.type === type) { return i; } diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index f9fc6005e7..cfaf31b054 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -272,7 +272,7 @@ describe('di', () => { describe('getOrCreateNodeInjector', () => { it('should handle initial undefined state', () => { - const contentView = createLView(-1, null !, []); + const contentView = createLView(-1, null !, {data: []}); const oldView = enterView(contentView, null !); try { const parent = createLNode(0, LNodeFlags.Element, null, null); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 922d28781f..2a5017f02d 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -604,22 +604,22 @@ describe('render3 integration test', () => { cr(); } - expect((Template as any).ngStaticData).toBeUndefined(); + expect((Template as any).ngPrivateData).toBeUndefined(); renderToHtml(Template, {condition: true}); - const oldTemplateData = (Template as any).ngStaticData; - const oldContainerData = (oldTemplateData as any)[0]; - const oldElementData = oldContainerData.containerStatic[0][0]; + const oldTemplateData = (Template as any).ngPrivateData; + const oldContainerData = (oldTemplateData as any).data[0]; + const oldElementData = oldContainerData.data[0][0]; expect(oldContainerData).not.toBeNull(); expect(oldElementData).not.toBeNull(); renderToHtml(Template, {condition: false}); renderToHtml(Template, {condition: true}); - const newTemplateData = (Template as any).ngStaticData; - const newContainerData = (oldTemplateData as any)[0]; - const newElementData = oldContainerData.containerStatic[0][0]; + const newTemplateData = (Template as any).ngPrivateData; + const newContainerData = (oldTemplateData as any).data[0]; + const newElementData = oldContainerData.data[0][0]; expect(newTemplateData === oldTemplateData).toBe(true); expect(newContainerData === oldContainerData).toBe(true); expect(newElementData === oldElementData).toBe(true); diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index bf5ee2a913..de4570774b 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -18,7 +18,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): TNode { initialInputs: undefined, inputs: undefined, outputs: undefined, - containerStatic: null, + data: null, }; }