From be0daf0b0d27575bb64359013b2303d97a08f472 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Thu, 7 Jun 2018 22:42:32 -0700 Subject: [PATCH 1/4] refactor(ivy): combine LView with data --- packages/core/src/render3/component.ts | 23 +- packages/core/src/render3/component_ref.ts | 12 +- packages/core/src/render3/di.ts | 42 +- packages/core/src/render3/hooks.ts | 18 +- packages/core/src/render3/instructions.ts | 588 +++++++++--------- .../core/src/render3/interfaces/container.ts | 145 +++-- packages/core/src/render3/interfaces/node.ts | 12 +- packages/core/src/render3/interfaces/view.ts | 243 ++++---- .../core/src/render3/node_manipulation.ts | 167 +++-- packages/core/src/render3/pipe.ts | 11 +- packages/core/src/render3/query.ts | 7 +- packages/core/src/render3/view_ref.ts | 17 +- .../hello_world/bundle.golden_symbols.json | 50 +- .../bundling/todo/bundle.golden_symbols.json | 65 +- packages/core/test/render3/di_spec.ts | 4 +- .../core/test/render3/integration_spec.ts | 12 +- 16 files changed, 766 insertions(+), 650 deletions(-) diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index cfbc386e2d07e..8150df4a9880b 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -10,19 +10,16 @@ // correctly implementing its interfaces for backwards compatibility. import {Type} from '../core'; import {Injector} from '../di/injector'; -import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {Sanitizer} from '../sanitization/security'; import {assertComponentType, assertDefined} from './assert'; import {queueInitHooks, queueLifecycleHooks} from './hooks'; -import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings} from './instructions'; +import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings,} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; -import {LElementNode, TNodeFlags} from './interfaces/node'; +import {LElementNode} from './interfaces/node'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; -import {LView, LViewFlags, RootContext} from './interfaces/view'; +import {LViewData, LViewFlags, RootContext, INJECTOR, CONTEXT, TVIEW} from './interfaces/view'; import {stringify} from './util'; -import {ViewRef} from './view_ref'; - /** Options that control how the component should be bootstrapped. */ @@ -71,7 +68,6 @@ export interface CreateComponentOptions { scheduler?: (work: () => void) => void; } - // TODO: A hack to not pull in the NullInjector from @angular/core. export const NULL_INJECTOR: Injector = { get: (token: any, notFoundValue?: any) => { @@ -108,11 +104,11 @@ export function renderComponent( const hostNode = locateHostElement(rendererFactory, opts.host || componentTag); const rootContext = createRootContext(opts.scheduler || requestAnimationFrame.bind(window)); - const rootView: LView = createLView( + const rootView: LViewData = createViewData( rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(-1, null, null, null), rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); - rootView.injector = opts.injector || null; + rootView[INJECTOR] = opts.injector || null; const oldView = enterView(rootView, null !); let elementNode: LElementNode; @@ -131,7 +127,7 @@ export function renderComponent( executeInitAndContentHooks(); setHostBindings(ROOT_DIRECTIVE_INDICES); - detectChangesInternal(elementNode.data as LView, elementNode, component); + detectChangesInternal(elementNode.data as LViewData, elementNode, component); } finally { leaveView(oldView); if (rendererFactory.end) rendererFactory.end(); @@ -165,8 +161,9 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef): v const elementNode = _getComponentHostLElementNode(component); // Root component is always created at dir index 0 - queueInitHooks(0, def.onInit, def.doCheck, elementNode.view.tView); - queueLifecycleHooks(elementNode.tNode.flags, elementNode.view); + const tView = elementNode.view[TVIEW]; + queueInitHooks(0, def.onInit, def.doCheck, tView); + queueLifecycleHooks(elementNode.tNode.flags, tView); } /** @@ -176,7 +173,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef): v * @param component any component */ function getRootContext(component: any): RootContext { - const rootContext = getRootView(component).context as RootContext; + const rootContext = getRootView(component)[CONTEXT] as RootContext; ngDevMode && assertDefined(rootContext, 'rootContext'); return rootContext; } diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 01222b5a3b1c5..da04e9c126a89 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -18,11 +18,11 @@ import {Type} from '../type'; import {assertComponentType, assertDefined} from './assert'; import {createRootContext} from './component'; -import {baseDirectiveCreate, createLView, createTView, enterView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement} from './instructions'; +import {baseDirectiveCreate, createTView, createViewData, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; import {LElementNode} from './interfaces/node'; import {RElement} from './interfaces/renderer'; -import {LView, LViewFlags, RootContext} from './interfaces/view'; +import {INJECTOR, LViewData, LViewFlags, RootContext} from './interfaces/view'; import {ViewRef} from './view_ref'; export class ComponentFactoryResolver extends viewEngine_ComponentFactoryResolver { @@ -94,11 +94,11 @@ export class ComponentFactory extends viewEngine_ComponentFactory { const rootContext: RootContext = ngModule !.injector.get(ROOT_CONTEXT); // Create the root view. Uses empty TView and ContentTemplate. - const rootView: LView = createLView( + const rootView: LViewData = createViewData( rendererFactory.createRenderer(hostNode, this.componentDef.rendererType), createTView(-1, null, null, null), null, this.componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); - rootView.injector = ngModule && ngModule.injector || null; + rootView[INJECTOR] = ngModule && ngModule.injector || null; // rootView is the parent when bootstrapping const oldView = enterView(rootView, null !); @@ -145,7 +145,7 @@ export class ComponentRef extends viewEngine_ComponentRef { componentType: Type; constructor( - componentType: Type, instance: T, rootView: LView, injector: Injector, + componentType: Type, instance: T, rootView: LViewData, injector: Injector, hostNode: RElement) { super(); this.instance = instance; @@ -173,4 +173,4 @@ export class ComponentRef extends viewEngine_ComponentRef { ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed'); this.destroyCbs !.push(callback); } -} \ No newline at end of file +} diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index a8a800f846a3a..c6bac64a82610 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -19,13 +19,14 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie import {Type} from '../type'; import {assertDefined, assertGreaterThan, assertLessThan} from './assert'; -import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; -import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; +import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; +import {VIEWS} from './interfaces/container'; +import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node'; import {LQueries, QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; -import {LView, TView} from './interfaces/view'; +import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {detachView, getParentLNode, insertView, removeView} from './node_manipulation'; import {notImplemented, stringify} from './util'; @@ -282,9 +283,9 @@ export function getOrCreateChangeDetectorRef( const currentNode = di.node; if (isComponent(currentNode.tNode)) { - return di.changeDetectorRef = new ViewRef(currentNode.data as LView, context); + return di.changeDetectorRef = new ViewRef(currentNode.data as LViewData, context); } else if (currentNode.tNode.type === TNodeType.Element) { - return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node); + return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view[HOST_NODE]); } return null !; } @@ -299,9 +300,9 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): return existingRef ? existingRef : new ViewRef( - hostNode.data as LView, - hostNode.view - .directives ![hostNode.tNode.flags >> TNodeFlags.DirectiveStartingIndexShift]); + hostNode.data as LViewData, + hostNode + .view[DIRECTIVES] ![hostNode.tNode.flags >> TNodeFlags.DirectiveStartingIndexShift]); } /** @@ -311,7 +312,7 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): */ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNode { while (node.tNode.type === TNodeType.View) { - node = node.view.node; + node = node.view[HOST_NODE]; } return node as LElementNode; } @@ -340,7 +341,7 @@ export function getOrCreateInjectable( // If the token has a bloom hash, then it is a directive that is public to the injection system // (diPublic). If there is no hash, fall back to the module injector. if (bloomHash === null) { - const moduleInjector = getPreviousOrParentNode().view.injector; + const moduleInjector = getPreviousOrParentNode().view[INJECTOR]; const formerInjector = setCurrentInjector(moduleInjector); try { return inject(token, flags); @@ -370,14 +371,14 @@ export function getOrCreateInjectable( if (count !== 0) { const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; - const defs = node.view.tView.directives !; + const defs = node.view[TVIEW].directives !; for (let i = start; i < end; 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 = defs[i] as DirectiveDef; if (directiveDef.type === token && directiveDef.diPublic) { - return getDirectiveInstance(node.view.directives ![i]); + return getDirectiveInstance(node.view[DIRECTIVES] ![i]); } } } @@ -405,12 +406,12 @@ export function getOrCreateInjectable( } function searchMatchesQueuedForCreation(node: LNode, token: any): T|null { - const matches = node.view.tView.currentMatches; + const matches = node.view[TVIEW].currentMatches; if (matches) { for (let i = 0; i < matches.length; i += 2) { const def = matches[i] as DirectiveDef; if (def.type === token) { - return resolveDirective(def, i + 1, matches, node.view.tView); + return resolveDirective(def, i + 1, matches, node.view[TVIEW]); } } } @@ -545,7 +546,7 @@ export const QUERY_READ_FROM_NODE = (new ReadFromInjectorFn((injector: LInjector, node: LNode, directiveIdx: number) => { ngDevMode && assertNodeOfPossibleTypes(node, TNodeType.Container, TNodeType.Element); if (directiveIdx > -1) { - return node.view.directives ![directiveIdx]; + return node.view[DIRECTIVES] ![directiveIdx]; } else if (node.tNode.type === TNodeType.Element) { return getOrCreateElementRef(injector); } else if (node.tNode.type === TNodeType.Container) { @@ -611,7 +612,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { clear(): void { const lContainer = this._lContainerNode.data; - while (lContainer.views.length) { + while (lContainer[VIEWS].length) { this.remove(0); } } @@ -620,7 +621,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { get length(): number { const lContainer = this._lContainerNode.data; - return lContainer.views.length; + return lContainer[VIEWS].length; } createEmbeddedView(templateRef: viewEngine_TemplateRef, context?: C, index?: number): @@ -679,12 +680,12 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { private _adjustIndex(index?: number, shift: number = 0) { if (index == null) { - return this._lContainerNode.data.views.length + shift; + return this._lContainerNode.data[VIEWS].length + shift; } if (ngDevMode) { assertGreaterThan(index, -1, 'index must be positive'); // +1 because it's legal to insert at the end. - assertLessThan(index, this._lContainerNode.data.views.length + 1 + shift, 'index'); + assertLessThan(index, this._lContainerNode.data[VIEWS].length + 1 + shift, 'index'); } return index; } @@ -704,7 +705,8 @@ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef const hostTNode = hostNode.tNode; ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated'); di.templateRef = new TemplateRef( - getOrCreateElementRef(di), hostTNode.tViews as TView, getRenderer(), hostNode.data.queries); + getOrCreateElementRef(di), hostTNode.tViews as TView, getRenderer(), + hostNode.data[QUERIES]); } return di.templateRef; } diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index 66ba2b0de1e5c..5b49b24b7845b 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -9,7 +9,7 @@ import {assertEqual} from './assert'; import {DirectiveDef} from './interfaces/definition'; import {TNodeFlags} from './interfaces/node'; -import {HookData, LView, LViewFlags, TView} from './interfaces/view'; +import {DIRECTIVES, FLAGS, HookData, LViewData, LViewFlags, TView} from './interfaces/view'; /** @@ -20,7 +20,7 @@ import {HookData, LView, LViewFlags, TView} from './interfaces/view'; * directive index), then saved in the even indices of the initHooks array. The odd indices * hold the hook functions themselves. * - * @param index The index of the directive in LView.data + * @param index The index of the directive in LViewData[DIRECTIVES] * @param hooks The static hooks map on the directive def * @param tView The current TView */ @@ -42,9 +42,8 @@ export function queueInitHooks( * Loops through the directives on a node and queues all their hooks except ngOnInit * and ngDoCheck, which are queued separately in directiveCreate. */ -export function queueLifecycleHooks(flags: number, currentView: LView): void { - const tView = currentView.tView; - if (tView.firstTemplatePass === true) { +export function queueLifecycleHooks(flags: number, tView: TView): void { + if (tView.firstTemplatePass) { const start = flags >> TNodeFlags.DirectiveStartingIndexShift; const count = flags & TNodeFlags.DirectiveCountMask; const end = start + count; @@ -97,10 +96,11 @@ function queueDestroyHooks(def: DirectiveDef, tView: TView, i: number): voi * * @param currentView The current view */ -export function executeInitHooks(currentView: LView, tView: TView, creationMode: boolean): void { - if (currentView.flags & LViewFlags.RunInit) { - executeHooks(currentView.directives !, tView.initHooks, tView.checkHooks, creationMode); - currentView.flags &= ~LViewFlags.RunInit; +export function executeInitHooks( + currentView: LViewData, tView: TView, creationMode: boolean): void { + if (currentView[FLAGS] & LViewFlags.RunInit) { + executeHooks(currentView[DIRECTIVES] !, tView.initHooks, tView.checkHooks, creationMode); + currentView[FLAGS] &= ~LViewFlags.RunInit; } } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index e14ee63070e93..302acde821b63 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -13,17 +13,17 @@ import {Sanitizer} from '../sanitization/security'; import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotEqual, assertSame} from './assert'; import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; -import {LContainer} from './interfaces/container'; +import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; import {LInjector} from './interfaces/injector'; import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; -import {CurrentMatchesList, LView, LViewFlags, RootContext, TData, TView} from './interfaces/view'; +import {BINDING_INDEX, CLEANUP, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView,} from './interfaces/view'; import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; import {ViewRef} from './view_ref'; @@ -87,7 +87,7 @@ export function getRenderer(): Renderer3 { } export function getCurrentSanitizer(): Sanitizer|null { - return currentView && currentView.sanitizer; + return viewData && viewData[SANITIZER]; } /** Used to set the parent property when nodes are created. */ @@ -105,24 +105,7 @@ export function getPreviousOrParentNode(): LNode { */ let isParent: boolean; -/** - * 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. Any nodes that do not have static data store a null value in - * tData to avoid a sparse array. - */ -let tData: TData; - -/** - * State of the current view being processed. - * - * NOTE: we cheat here and initialize it to `null` even thought the type does not - * contain `null`. This is because we expect this value to be not `null` as soon - * as we enter the view. Declaring the type as `null` would require us to place `!` - * in most instructions since they all assume that `currentView` is defined. - */ -let currentView: LView = null !; +let tView: TView; let currentQueries: LQueries|null; @@ -142,10 +125,12 @@ export function getCreationMode(): boolean { } /** + * State of the current view being processed. + * * An array of nodes (text, element, container, etc), pipes, their bindings, and * any local variables that need to be stored between invocations. */ -let data: any[]; +let viewData: LViewData; /** * An array of directive instances in the current view. @@ -155,13 +140,13 @@ let data: any[]; */ let directives: any[]|null; -function getCleanup(view: LView): any[] { +function getCleanup(view: LViewData): any[] { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return view.cleanupInstances || (view.cleanupInstances = []); + return view[CLEANUP] || (view[CLEANUP] = []); } -function getTViewCleanup(view: LView): any[] { - return view.tView.cleanup || (view.tView.cleanup = []); +function getTViewCleanup(view: LViewData): any[] { + return view[TVIEW].cleanup || (view[TVIEW].cleanup = []); } /** * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error. @@ -190,23 +175,23 @@ const enum BindingDirection { * @param host Element to which the View is a child of * @returns the previous state; */ -export function enterView(newView: LView, host: LElementNode | LViewNode | null): LView { - const oldView: LView = currentView; - data = newView && newView.data; - directives = newView && newView.directives; - tData = newView && newView.tView.data; - creationMode = newView && (newView.flags & LViewFlags.CreationMode) === LViewFlags.CreationMode; - firstTemplatePass = newView && newView.tView.firstTemplatePass; +export function enterView(newView: LViewData, host: LElementNode | LViewNode | null): LViewData { + const oldView: LViewData = viewData; + directives = newView && newView[DIRECTIVES]; + tView = newView && newView[TVIEW]; + + creationMode = newView && (newView[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode; + firstTemplatePass = newView && tView.firstTemplatePass; - renderer = newView && newView.renderer; + renderer = newView && newView[RENDERER]; if (host != null) { previousOrParentNode = host; isParent = true; } - currentView = newView; - currentQueries = newView && newView.queries; + viewData = newView; + currentQueries = newView && newView[QUERIES]; return oldView; } @@ -219,18 +204,16 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null) * @param creationOnly An optional boolean to indicate that the view was processed in creation mode * only, i.e. the first update will be done later. Only possible for dynamically created views. */ -export function leaveView(newView: LView, creationOnly?: boolean): void { +export function leaveView(newView: LViewData, creationOnly?: boolean): void { if (!creationOnly) { if (!checkNoChangesMode) { - executeHooks( - directives !, currentView.tView.viewHooks, currentView.tView.viewCheckHooks, - creationMode); + executeHooks(directives !, tView.viewHooks, tView.viewCheckHooks, creationMode); } // Views are clean and in update mode after being checked, so these bits are cleared - currentView.flags &= ~(LViewFlags.CreationMode | LViewFlags.Dirty); + viewData[FLAGS] &= ~(LViewFlags.CreationMode | LViewFlags.Dirty); } - currentView.flags |= LViewFlags.RunInit; - currentView.bindingIndex = -1; + viewData[FLAGS] |= LViewFlags.RunInit; + viewData[BINDING_INDEX] = -1; enterView(newView, null); } @@ -241,9 +224,8 @@ export function leaveView(newView: LView, creationOnly?: boolean): void { * Note: view hooks are triggered later when leaving the view. * */ function refreshView() { - const tView = currentView.tView; if (!checkNoChangesMode) { - executeInitHooks(currentView, tView, creationMode); + executeInitHooks(viewData, tView, creationMode); } refreshDynamicChildren(); if (!checkNoChangesMode) { @@ -260,7 +242,7 @@ function refreshView() { /** Sets the host bindings for the current view. */ export function setHostBindings(bindings: number[] | null): void { if (bindings != null) { - const defs = currentView.tView.directives !; + const defs = tView.directives !; for (let i = 0; i < bindings.length; i += 2) { const dirIndex = bindings[i]; const def = defs[dirIndex] as DirectiveDef; @@ -280,34 +262,30 @@ function refreshChildComponents(components: number[] | null): void { export function executeInitAndContentHooks(): void { if (!checkNoChangesMode) { - const tView = currentView.tView; - executeInitHooks(currentView, tView, creationMode); + executeInitHooks(viewData, tView, creationMode); executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode); } } -export function createLView( +export function createViewData( renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags, - sanitizer?: Sanitizer | null): LView { - const newView = { - parent: currentView, - flags: flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit, - node: null !, // until we initialize it in createNode. - data: [], - directives: null, - tView: tView, - cleanupInstances: null, - renderer: renderer, - tail: null, - next: null, - bindingIndex: -1, - context: context, - queries: null, - injector: currentView && currentView.injector, - sanitizer: sanitizer || null - }; - - return newView; + sanitizer?: Sanitizer | null): LViewData { + return [ + null, // next + viewData, // parent + null, // queries + tView, // tView + flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit, // flags + null !, // hostNode + -1, // bindingIndex + null, // directives + null, // cleanupInstances + context, // context + viewData && viewData[INJECTOR], // injector + renderer, // renderer + sanitizer || null, // sanitizer + null // tail + ]; } /** @@ -316,7 +294,7 @@ export function createLView( * (same properties assigned in the same order). */ export function createLNodeObject( - type: TNodeType, currentView: LView, parent: LNode | null, + type: TNodeType, currentView: LViewData, parent: LNode | null, native: RText | RElement | null | undefined, state: any, queries: LQueries | null): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { return { @@ -336,7 +314,7 @@ export function createLNodeObject( * keep the execution code monomorphic and fast. * * @param index The index at which the LNode should be saved (null if view, since they are not - * saved) + * saved). * @param type The type of LNode to create * @param native The native element for this LNode, if applicable * @param name The tag name of the associated native element, if applicable @@ -345,10 +323,10 @@ export function createLNodeObject( */ export function createLNode( index: number, type: TNodeType.Element, native: RElement | RText | null, name: string | null, - attrs: TAttributes | null, lView?: LView | null): LElementNode; + attrs: TAttributes | null, lViewData?: LViewData | null): LElementNode; export function createLNode( index: number, type: TNodeType.View, native: null, name: null, attrs: null, - lView: LView): LViewNode; + lViewData: LViewData): LViewNode; export function createLNode( index: number, type: TNodeType.Container, native: undefined, name: string | null, attrs: TAttributes | null, lContainer: LContainer): LContainerNode; @@ -357,45 +335,51 @@ export function createLNode( lProjection: LProjection): LProjectionNode; export function createLNode( index: number, type: TNodeType, native: RText | RElement | null | undefined, - name: string | null, attrs: TAttributes | null, state?: null | LView | LContainer | + name: string | null, attrs: TAttributes | null, state?: null | LViewData | LContainer | LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { const parent = isParent ? previousOrParentNode : previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode; // Parents cannot cross component boundaries because components will be used in multiple places, // so it's only set if the view is the same. const tParent = - parent && parent.view === currentView ? parent.tNode as TElementNode | TContainerNode : null; + parent && parent.view === viewData ? parent.tNode as TElementNode | TContainerNode : null; let queries = (isParent ? currentQueries : previousOrParentNode && previousOrParentNode.queries) || parent && parent.queries && parent.queries.child(); const isState = state != null; const node = - createLNodeObject(type, currentView, parent, native, isState ? state as any : null, queries); + createLNodeObject(type, viewData, parent, native, isState ? state as any : null, queries); if (index === -1 || type === TNodeType.View) { // 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. - node.tNode = (state as LView).tView.node || createTNode(type, index, null, null, tParent, null); + node.tNode = + (state as LViewData)[TVIEW].node || createTNode(type, index, null, null, tParent, null); } else { + const adjustedIndex = index + HEADER_OFFSET; + // This is an element or container or projection node - ngDevMode && assertDataNext(index); - data[index] = node; + ngDevMode && assertDataNext(adjustedIndex); + const tData = tView.data; + + viewData[adjustedIndex] = node; // Every node adds a value to the static data array to avoid a sparse array - if (index >= tData.length) { - const tNode = tData[index] = createTNode(type, index, name, attrs, tParent, null); + if (adjustedIndex >= tData.length) { + const tNode = tData[adjustedIndex] = + createTNode(type, adjustedIndex, name, attrs, tParent, null); if (!isParent && previousOrParentNode) { const previousTNode = previousOrParentNode.tNode; previousTNode.next = tNode; if (previousTNode.dynamicContainerNode) previousTNode.dynamicContainerNode.next = tNode; } } - node.tNode = tData[index] as TNode; + node.tNode = tData[adjustedIndex] as TNode; // Now link ourselves into the tree. if (isParent) { currentQueries = null; - if (previousOrParentNode.tNode.child == null && previousOrParentNode.view === currentView || + if (previousOrParentNode.tNode.child == null && previousOrParentNode.view === viewData || previousOrParentNode.tNode.type === TNodeType.View) { // We are in the same view, which means we are adding content node to the parent View. previousOrParentNode.tNode.child = node.tNode; @@ -405,12 +389,11 @@ export function createLNode( // View nodes and host elements need to set their host node (components set host nodes later) if ((type & TNodeType.ViewOrElement) === TNodeType.ViewOrElement && isState) { - // Bit of a hack to bust through the readonly because there is a circular dep between - // LView and LNode. - ngDevMode && - assertNotDefined((state as LView).node, 'LView.node should not have been initialized'); - (state as{node: LNode}).node = node; - if (firstTemplatePass) (state as LView).tView.node = node.tNode; + const lViewData = state as LViewData; + ngDevMode && assertNotDefined( + lViewData[HOST_NODE], 'lViewData[HOST_NODE] should not have been initialized'); + lViewData[HOST_NODE] = node; + if (firstTemplatePass) lViewData[TVIEW].node = node.tNode; } previousOrParentNode = node; @@ -452,7 +435,7 @@ export function renderTemplate( const tView = getOrCreateTView(template, directives || null, pipes || null); host = createLNode( -1, TNodeType.Element, hostNode, null, null, - createLView( + createViewData( providedRendererFactory.createRenderer(null, null), tView, {}, LViewFlags.CheckAlways, sanitizer)); } @@ -477,7 +460,7 @@ export function renderEmbeddedTemplate( queries?: LQueries | null): LViewNode { const _isParent = isParent; const _previousOrParentNode = previousOrParentNode; - let oldView: LView; + let oldView: LViewData; let rf: RenderFlags = RenderFlags.Update; try { isParent = true; @@ -485,10 +468,10 @@ export function renderEmbeddedTemplate( if (viewNode == null) { const lView = - createLView(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer()); + createViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer()); if (queries) { - lView.queries = queries.createView(); + lView[QUERIES] = queries.createView(); } viewNode = createLNode(-1, TNodeType.View, null, null, null, lView); @@ -499,7 +482,7 @@ export function renderEmbeddedTemplate( if (rf & RenderFlags.Update) { refreshView(); } else { - viewNode.data.tView.firstTemplatePass = firstTemplatePass = false; + viewNode.data[TVIEW].firstTemplatePass = firstTemplatePass = false; } } finally { // renderEmbeddedTemplate() is called twice in fact, once for creation only and then once for @@ -513,7 +496,8 @@ export function renderEmbeddedTemplate( } export function renderComponentOrTemplate( - node: LElementNode, hostView: LView, componentOrContext: T, template?: ComponentTemplate) { + node: LElementNode, hostView: LViewData, componentOrContext: T, + template?: ComponentTemplate) { const oldView = enterView(hostView, node); try { if (rendererFactory.begin) { @@ -528,7 +512,7 @@ export function renderComponentOrTemplate( // Element was stored at 0 in data and directive was stored at 0 in directives // in renderComponent() setHostBindings(_ROOT_DIRECTIVE_INDICES); - componentRefresh(0, 0); + componentRefresh(0, HEADER_OFFSET); } } finally { if (rendererFactory.end) { @@ -547,9 +531,9 @@ export function renderComponentOrTemplate( * Dynamically created views do NOT use this configuration (update block and create block are * always run separately). */ -function getRenderFlags(view: LView): RenderFlags { - return view.flags & LViewFlags.CreationMode ? RenderFlags.Create | RenderFlags.Update : - RenderFlags.Update; +function getRenderFlags(view: LViewData): RenderFlags { + return view[FLAGS] & LViewFlags.CreationMode ? RenderFlags.Create | RenderFlags.Update : + RenderFlags.Update; } ////////////////////////// @@ -559,7 +543,7 @@ function getRenderFlags(view: LView): RenderFlags { /** * Create DOM element. The instruction must later be followed by `elementEnd()` call. * - * @param index Index of the element in the data array + * @param index Index of the element in the LViewData array * @param name Name of the DOM Node * @param attrs Statically bound set of attributes to be written into the DOM element on creation. * @param localRefs A set of local reference bindings on the element. @@ -572,7 +556,7 @@ export function elementStart( index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): RElement { ngDevMode && - assertEqual(currentView.bindingIndex, -1, 'elements should be created before any bindings'); + assertEqual(viewData[BINDING_INDEX], -1, 'elements should be created before any bindings'); ngDevMode && ngDevMode.rendererCreateElement++; const native: RElement = renderer.createElement(name); @@ -582,7 +566,7 @@ export function elementStart( createLNode(index, TNodeType.Element, native !, name, attrs || null, null); if (attrs) setUpAttributes(native, attrs); - appendChild(getParentLNode(node), native, currentView); + appendChild(getParentLNode(node), native, viewData); createDirectivesAndLocals(localRefs); return native; } @@ -597,7 +581,7 @@ function createDirectivesAndLocals(localRefs?: string[] | null) { if (firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; - cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null); + cacheMatchingDirectivesForNode(node.tNode, tView, localRefs || null); } else { instantiateDirectivesDirectly(); } @@ -627,7 +611,7 @@ function cacheMatchingDirectivesForNode( /** Matches the current node against all available selectors. */ function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null { - const registry = currentView.tView.directiveRegistry; + const registry = tView.directiveRegistry; let matches: any[]|null = null; if (registry) { for (let i = 0; i < registry.length; i++) { @@ -662,23 +646,24 @@ export function resolveDirective( /** Stores index of component's host element so it will be queued for view refresh during CD. */ function queueComponentIndexForCheck(dirIndex: number): void { if (firstTemplatePass) { - (currentView.tView.components || (currentView.tView.components = [ - ])).push(dirIndex, data.length - 1); + (tView.components || (tView.components = [])).push(dirIndex, viewData.length - 1); } } /** Stores index of directive and host element so it will be queued for binding refresh during CD. */ function queueHostBindingForCheck(dirIndex: number): void { + // Must subtract the header offset because hostBindings functions are generated with + // instructions that expect element indices that are NOT adjusted (e.g. elementProperty). ngDevMode && assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.'); - (currentView.tView.hostBindings || (currentView.tView.hostBindings = [ - ])).push(dirIndex, data.length - 1); + (tView.hostBindings || (tView.hostBindings = [ + ])).push(dirIndex, viewData.length - 1 - HEADER_OFFSET); } /** Sets the context for a ChangeDetectorRef to the given instance. */ export function initChangeDetectorIfExisting( - injector: LInjector | null, instance: any, view: LView): void { + injector: LInjector | null, instance: any, view: LViewData): void { if (injector && injector.changeDetectorRef != null) { (injector.changeDetectorRef as ViewRef)._setComponentContext(view, instance); } @@ -698,7 +683,7 @@ function instantiateDirectivesDirectly() { if (count > 0) { const start = tNode.flags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; - const tDirectives = currentView.tView.directives !; + const tDirectives = tView.directives !; for (let i = start; i < end; i++) { const def: DirectiveDef = tDirectives[i]; @@ -739,7 +724,7 @@ function saveNameToExportMap( /** * Takes a list of local names and indices and pushes the resolved local variable values - * to data[] in the same order as they are loaded in the template with load(). + * to LViewData in the same order as they are loaded in the template with load(). */ function saveResolvedLocalsInData(): void { const localNames = previousOrParentNode.tNode.localNames; @@ -747,7 +732,7 @@ function saveResolvedLocalsInData(): void { for (let i = 0; i < localNames.length; i += 2) { const index = localNames[i + 1] as number; const value = index === -1 ? previousOrParentNode.native : directives ![index]; - data.push(value); + viewData.push(value); } } } @@ -790,9 +775,9 @@ export function createTView( id: viewIndex, template: template, node: null !, - data: [], - childIndex: -1, // Children set in addToViewTree(), if any - bindingStartIndex: -1, // Set in initBindings() + data: new Array(HEADER_OFFSET).fill(null), // Fill in to match HEADER_OFFSET in LViewData + childIndex: -1, // Children set in addToViewTree(), if any + bindingStartIndex: -1, // Set in initBindings() directives: null, firstTemplatePass: true, initHooks: null, @@ -872,14 +857,14 @@ export function hostElement( resetApplicationState(); const node = createLNode( 0, TNodeType.Element, rNode, null, null, - createLView( + createViewData( renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); if (firstTemplatePass) { node.tNode.flags = TNodeFlags.isComponent; if (def.diPublic) def.diPublic(def); - currentView.tView.directives = [def]; + tView.directives = [def]; } return node; @@ -906,17 +891,17 @@ export function listener( // In order to match current behavior, native DOM event listeners must be added for all // events (including outputs). if (isProceduralRenderer(renderer)) { - const wrappedListener = wrapListenerWithDirtyLogic(currentView, listenerFn); + const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn); const cleanupFn = renderer.listen(native, eventName, wrappedListener); - storeCleanupFn(currentView, cleanupFn); + storeCleanupFn(viewData, cleanupFn); } else { - const wrappedListener = wrapListenerWithDirtyAndDefault(currentView, listenerFn); + const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn); native.addEventListener(eventName, wrappedListener, useCapture); - const cleanupInstances = getCleanup(currentView); + const cleanupInstances = getCleanup(viewData); cleanupInstances.push(wrappedListener); if (firstTemplatePass) { - getTViewCleanup(currentView) - .push(eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture); + getTViewCleanup(viewData).push( + eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture); } } @@ -942,7 +927,7 @@ function createOutput(outputs: PropertyAliasValue, listener: Function): void { for (let i = 0; i < outputs.length; i += 2) { ngDevMode && assertDataInRange(outputs[i] as number, directives !); const subscription = directives ![outputs[i] as number][outputs[i + 1]].subscribe(listener); - storeCleanupWithContext(currentView, subscription, subscription.unsubscribe); + storeCleanupWithContext(viewData, subscription, subscription.unsubscribe); } } @@ -954,11 +939,12 @@ function createOutput(outputs: PropertyAliasValue, listener: Function): void { * - Index of context we just saved in LView.cleanupInstances */ export function storeCleanupWithContext( - view: LView = currentView, context: any, cleanupFn: Function): void { + view: LViewData | null, context: any, cleanupFn: Function): void { + if (!view) view = viewData; getCleanup(view).push(context); - if (view.tView.firstTemplatePass) { - getTViewCleanup(view).push(cleanupFn, view.cleanupInstances !.length - 1); + if (view[TVIEW].firstTemplatePass) { + getTViewCleanup(view).push(cleanupFn, view[CLEANUP] !.length - 1); } } @@ -970,11 +956,11 @@ export function storeCleanupWithContext( * * On the first template pass, the index of the cleanup function is saved in TView. */ -export function storeCleanupFn(view: LView, cleanupFn: Function): void { +export function storeCleanupFn(view: LViewData, cleanupFn: Function): void { getCleanup(view).push(cleanupFn); - if (view.tView.firstTemplatePass) { - getTViewCleanup(view).push(view.cleanupInstances !.length - 1, null); + if (view[TVIEW].firstTemplatePass) { + getTViewCleanup(view).push(view[CLEANUP] !.length - 1, null); } } @@ -989,7 +975,7 @@ export function elementEnd() { ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Element); const queries = previousOrParentNode.queries; queries && queries.addNode(previousOrParentNode); - queueLifecycleHooks(previousOrParentNode.tNode.flags, currentView); + queueLifecycleHooks(previousOrParentNode.tNode.flags, tView); } /** @@ -1004,7 +990,7 @@ export function elementEnd() { export function elementAttribute( index: number, name: string, value: any, sanitizer?: SanitizerFn): void { if (value !== NO_CHANGE) { - const element: LElementNode = data[index]; + const element: LElementNode = load(index); if (value == null) { ngDevMode && ngDevMode.rendererRemoveAttribute++; isProceduralRenderer(renderer) ? renderer.removeAttribute(element.native, name) : @@ -1035,7 +1021,7 @@ export function elementAttribute( export function elementProperty( index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void { if (value === NO_CHANGE) return; - const node = data[index] as LElementNode; + const node = load(index) as LElementNode; const tNode = node.tNode; // if tNode.inputs is undefined, a listener has created outputs, but inputs haven't // yet been checked @@ -1065,7 +1051,7 @@ export function elementProperty( * Constructs a TNode object from the arguments. * * @param type The type of the node - * @param index The index of the TNode in TView.data + * @param adjustedIndex The index of the TNode in TView.data, adjusted for HEADER_OFFSET * @param tagName The tag name of the node * @param attrs The attributes defined on this node * @param parent The parent of this node @@ -1073,12 +1059,12 @@ export function elementProperty( * @returns the TNode object */ export function createTNode( - type: TNodeType, index: number, tagName: string | null, attrs: TAttributes | null, + type: TNodeType, adjustedIndex: number, tagName: string | null, attrs: TAttributes | null, parent: TElementNode | TContainerNode | null, tViews: TView[] | null): TNode { ngDevMode && ngDevMode.tNode++; return { type: type, - index: index, + index: adjustedIndex, flags: 0, tagName: tagName, attrs: attrs, @@ -1121,7 +1107,7 @@ function generatePropertyAliases( const start = tNodeFlags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; const isInput = direction === BindingDirection.Input; - const defs = currentView.tView.directives !; + const defs = tView.directives !; for (let i = start; i < end; i++) { const directiveDef = defs[i] as DirectiveDef; @@ -1153,7 +1139,7 @@ function generatePropertyAliases( */ export function elementClassNamed(index: number, className: string, value: T | NO_CHANGE): void { if (value !== NO_CHANGE) { - const lElement = data[index] as LElementNode; + const lElement = load(index) as LElementNode; if (value) { ngDevMode && ngDevMode.rendererAddClass++; isProceduralRenderer(renderer) ? renderer.addClass(lElement.native, className) : @@ -1184,7 +1170,7 @@ export function elementClass(index: number, value: T | NO_CHANGE): void { // TODO: This is a naive implementation which simply writes value to the `className`. In the // future // we will add logic here which would work with the animation code. - const lElement: LElementNode = data[index]; + const lElement: LElementNode = load(index); ngDevMode && ngDevMode.rendererSetClassName++; isProceduralRenderer(renderer) ? renderer.setProperty(lElement.native, 'className', value) : lElement.native['className'] = stringify(value); @@ -1210,7 +1196,7 @@ export function elementStyleNamed( index: number, styleName: string, value: T | NO_CHANGE, suffixOrSanitizer?: string | SanitizerFn): void { if (value !== NO_CHANGE) { - const lElement: LElementNode = data[index]; + const lElement: LElementNode = load(index); if (value == null) { ngDevMode && ngDevMode.rendererRemoveStyle++; isProceduralRenderer(renderer) ? @@ -1234,7 +1220,7 @@ export function elementStyleNamed( * This instruction is meant to handle the `[style]="exp"` usage. * * - * @param index The index of the element to update in the data array + * @param index The index of the element to update in the LViewData array * @param value A value indicating if a given style should be added or removed. * The expected shape of `value` is an object where keys are style names and the values * are their corresponding values to set. If value is falsy, then the style is removed. An absence @@ -1246,7 +1232,7 @@ export function elementStyle( if (value !== NO_CHANGE) { // TODO: This is a naive implementation which simply writes value to the `style`. In the future // we will add logic here which would work with the animation code. - const lElement = data[index] as LElementNode; + const lElement = load(index) as LElementNode; if (isProceduralRenderer(renderer)) { ngDevMode && ngDevMode.rendererSetStyle++; renderer.setProperty(lElement.native, 'style', value); @@ -1276,19 +1262,19 @@ export function elementStyle( /** * Create static text node * - * @param index Index of the node in the data array. + * @param index Index of the node in the data array * @param value Value to write. This value will be stringified. */ export function text(index: number, value?: any): void { ngDevMode && - assertEqual(currentView.bindingIndex, -1, 'text nodes should be created before bindings'); + assertEqual(viewData[BINDING_INDEX], -1, 'text nodes should be created before bindings'); ngDevMode && ngDevMode.rendererCreateTextNode++; const textNode = createTextNode(value, renderer); const node = createLNode(index, TNodeType.Element, textNode, null, null); // Text nodes are self closing. isParent = false; - appendChild(getParentLNode(node), textNode, currentView); + appendChild(getParentLNode(node), textNode, viewData); } /** @@ -1300,8 +1286,8 @@ export function text(index: number, value?: any): void { */ export function textBinding(index: number, value: T | NO_CHANGE): void { if (value !== NO_CHANGE) { - ngDevMode && assertDataInRange(index); - const existingNode = data[index] as LTextNode; + ngDevMode && assertDataInRange(index + HEADER_OFFSET); + const existingNode = load(index) as LTextNode; ngDevMode && assertDefined(existingNode, 'LNode should exist'); ngDevMode && assertDefined(existingNode.native, 'native element should exist'); ngDevMode && ngDevMode.rendererSetText++; @@ -1338,7 +1324,7 @@ export function directiveCreate( if (firstTemplatePass) { // Init hooks are queued now so ngOnInit is called in host components before // any projected components. - queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView); + queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, tView); if (directiveDef.hostBindings) queueHostBindingForCheck(index); } @@ -1350,27 +1336,26 @@ export function directiveCreate( return instance; } -function addComponentLogic(index: number, instance: T, def: ComponentDef): void { +function addComponentLogic(directiveIndex: number, instance: T, def: ComponentDef): void { const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs); // Only component views should be added to the view tree directly. Embedded views are // accessed through their containers because they may be removed / re-added later. - const hostView = addToViewTree( - currentView, previousOrParentNode.tNode.index as number, - createLView( + const componentView = addToViewTree( + viewData, previousOrParentNode.tNode.index as number, + createViewData( rendererFactory.createRenderer(previousOrParentNode.native as RElement, def.rendererType), tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer())); // We need to set the host node/data here because when the component LNode was created, // we didn't yet know it was a component (just an element). - (previousOrParentNode as{data: LView}).data = hostView; - (hostView as{node: LNode}).node = previousOrParentNode; - if (firstTemplatePass) tView.node = previousOrParentNode.tNode; + (previousOrParentNode as{data: LViewData}).data = componentView; + (componentView as LViewData)[HOST_NODE] = previousOrParentNode as LElementNode; - initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, hostView); + initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, componentView); - if (firstTemplatePass) queueComponentIndexForCheck(index); + if (firstTemplatePass) queueComponentIndexForCheck(directiveIndex); } /** @@ -1382,13 +1367,13 @@ function addComponentLogic(index: number, instance: T, def: ComponentDef): export function baseDirectiveCreate( index: number, directive: T, directiveDef: DirectiveDef| ComponentDef): T { ngDevMode && - assertEqual(currentView.bindingIndex, -1, 'directives should be created before any bindings'); + assertEqual(viewData[BINDING_INDEX], -1, 'directives should be created before any bindings'); ngDevMode && assertPreviousIsParent(); Object.defineProperty( directive, NG_HOST_SYMBOL, {enumerable: false, value: previousOrParentNode}); - if (directives == null) currentView.directives = directives = []; + if (directives == null) viewData[DIRECTIVES] = directives = []; ngDevMode && assertDataNext(index, directives); directives[index] = directive; @@ -1489,23 +1474,21 @@ function generateInitialInputs( * * @param parentLNode the LNode in which the container's content will be rendered * @param currentView The parent view of the LContainer - * @param template Optional the inline template (ng-template instruction case) * @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case * @returns LContainer */ export function createLContainer( - parentLNode: LNode, currentView: LView, isForViewContainerRef?: boolean): LContainer { + parentLNode: LNode, currentView: LViewData, isForViewContainerRef?: boolean): LContainer { ngDevMode && assertDefined(parentLNode, 'containers should have a parent'); - return { - views: [], - nextIndex: isForViewContainerRef ? null : 0, - // If the direct parent of the container is a view, its views will need to be added - // through insertView() when its parent view is being inserted: - renderParent: canInsertNativeNode(parentLNode, currentView) ? parentLNode : null, - next: null, - parent: currentView, - queries: null - }; + return [ + null, // next + currentView, // parent + null, // queries + isForViewContainerRef ? null : 0, // active index + [], // views + canInsertNativeNode(parentLNode, currentView) ? parentLNode as LElementNode : + null // renderParent + ]; } /** @@ -1524,28 +1507,27 @@ export function container( localRefs?: string[] | null): void { ngDevMode && assertEqual( - currentView.bindingIndex, -1, 'container nodes should be created before any bindings'); + viewData[BINDING_INDEX], -1, 'container nodes should be created before any bindings'); const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !; - const lContainer = createLContainer(currentParent, currentView); + const lContainer = createLContainer(currentParent, viewData); const node = createLNode( index, TNodeType.Container, undefined, tagName || null, attrs || null, lContainer); if (firstTemplatePass) { - const tView = currentView.tView; node.tNode.tViews = template ? createTView(-1, template, tView.directiveRegistry, tView.pipeRegistry) : []; } // Containers are added to the current view tree instead of their embedded views // because views can be removed and re-inserted. - addToViewTree(currentView, index, node.data); + addToViewTree(viewData, index + HEADER_OFFSET, node.data); const queries = node.queries; if (queries) { // prepare place for matching nodes from views inserted into a given container - lContainer.queries = queries.container(); + lContainer[QUERIES] = queries.container(); } createDirectivesAndLocals(localRefs); @@ -1564,11 +1546,10 @@ export function container( * @param index The index of the container in the data array */ export function containerRefreshStart(index: number): void { - ngDevMode && assertDataInRange(index); - previousOrParentNode = data[index] as LNode; + previousOrParentNode = load(index) as LNode; ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); isParent = true; - (previousOrParentNode as LContainerNode).data.nextIndex = 0; + (previousOrParentNode as LContainerNode).data[ACTIVE_INDEX] = 0; ngDevMode && assertSame( (previousOrParentNode as LContainerNode).native, undefined, `the container's native element should not have been set yet.`); @@ -1576,7 +1557,7 @@ export function containerRefreshStart(index: number): void { if (!checkNoChangesMode) { // We need to execute init hooks here so ngOnInit hooks are called in top level views // before they are called in embedded views (for backwards compatibility). - executeInitHooks(currentView, currentView.tView, creationMode); + executeInitHooks(viewData, tView, creationMode); } } @@ -1597,34 +1578,33 @@ export function containerRefreshEnd(): void { const container = previousOrParentNode as LContainerNode; container.native = undefined; ngDevMode && assertNodeType(container, TNodeType.Container); - const nextIndex = container.data.nextIndex !; + const nextIndex = container.data[ACTIVE_INDEX] !; // remove extra views at the end of the container - while (nextIndex < container.data.views.length) { + while (nextIndex < container.data[VIEWS].length) { removeView(container, nextIndex); } } function refreshDynamicChildren() { - for (let current = getLViewChild(currentView); current !== null; current = current.next) { - // Note: current can be a LView or a LContainer, but here we are only interested in LContainer. - // The distinction is made because nextIndex and views do not exist on LView. - if (isLContainer(current)) { + for (let current = getLViewChild(viewData); current !== null; current = current[NEXT]) { + // Note: current can be an LViewData or an LContainer instance, but here we are only interested + // in LContainer. We can tell it's an LContainer because its length is less than the LViewData + // header. + if (current.length < HEADER_OFFSET && current[ACTIVE_INDEX] === null) { const container = current as LContainer; - for (let i = 0; i < container.views.length; i++) { - const lViewNode = container.views[i]; + for (let i = 0; i < container[VIEWS].length; i++) { + const lViewNode = container[VIEWS][i]; // The directives and pipes are not needed here as an existing view is only being refreshed. - const dynamicView = lViewNode.data; - ngDevMode && assertDefined(dynamicView.tView, 'TView must be allocated'); - renderEmbeddedTemplate(lViewNode, dynamicView.tView, dynamicView.context !, renderer); + const dynamicViewData = lViewNode.data; + ngDevMode && assertDefined(dynamicViewData[TVIEW], 'TView must be allocated'); + renderEmbeddedTemplate( + lViewNode, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !, renderer); } } } } -function isLContainer(node: LView | LContainer): node is LContainer { - return (node as LContainer).nextIndex == null && (node as LContainer).views != null; -} /** * Looks for a view with a given view block id inside a provided LContainer. @@ -1637,9 +1617,9 @@ function isLContainer(node: LView | LContainer): node is LContainer { */ function scanForView( containerNode: LContainerNode, startIdx: number, viewBlockId: number): LViewNode|null { - const views = containerNode.data.views; + const views = containerNode.data[VIEWS]; for (let i = startIdx; i < views.length; i++) { - const viewAtPositionId = views[i].data.tView.id; + const viewAtPositionId = views[i].data[TVIEW].id; if (viewAtPositionId === viewBlockId) { return views[i]; } else if (viewAtPositionId < viewBlockId) { @@ -1666,7 +1646,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { (isParent ? previousOrParentNode : getParentLNode(previousOrParentNode)) as LContainerNode; ngDevMode && assertNodeType(container, TNodeType.Container); const lContainer = container.data; - let viewNode: LViewNode|null = scanForView(container, lContainer.nextIndex !, viewBlockId); + let viewNode: LViewNode|null = scanForView(container, lContainer[ACTIVE_INDEX] !, viewBlockId); if (viewNode) { previousOrParentNode = viewNode; @@ -1675,12 +1655,12 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { enterView(viewNode.data, viewNode); } else { // When we create a new LView, we always reset the state of the instructions. - const newView = createLView( + const newView = createViewData( renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, LViewFlags.CheckAlways, getCurrentSanitizer()); - if (lContainer.queries) { - newView.queries = lContainer.queries.createView(); + if (lContainer[QUERIES]) { + newView[QUERIES] = lContainer[QUERIES] !.createView(); } enterView( @@ -1706,7 +1686,6 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV ngDevMode && assertDefined(containerTViews, 'TView expected'); ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array'); if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) { - const tView = currentView.tView; containerTViews[viewIndex] = createTView(viewIndex, null, tView.directiveRegistry, tView.pipeRegistry); } @@ -1717,7 +1696,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV export function embeddedViewEnd(): void { refreshView(); isParent = false; - const viewNode = previousOrParentNode = currentView.node as LViewNode; + const viewNode = previousOrParentNode = viewData[HOST_NODE] as LViewNode; const containerNode = getParentLNode(previousOrParentNode) as LContainerNode; if (containerNode) { ngDevMode && assertNodeType(viewNode, TNodeType.View); @@ -1727,14 +1706,14 @@ export function embeddedViewEnd(): void { if (creationMode) { // When projected nodes are going to be inserted, the renderParent of the dynamic container // used by the ViewContainerRef must be set. - setRenderParentInProjectedNodes(lContainer.renderParent, viewNode); + setRenderParentInProjectedNodes(lContainer[RENDER_PARENT], viewNode); // it is a new view, insert it into collection of views for a given container - insertView(containerNode, viewNode, lContainer.nextIndex !); + insertView(containerNode, viewNode, lContainer[ACTIVE_INDEX] !); } - lContainer.nextIndex !++; + lContainer[ACTIVE_INDEX] !++; } - leaveView(currentView !.parent !); + leaveView(viewData[PARENT]); ngDevMode && assertEqual(isParent, false, 'isParent'); ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); } @@ -1755,7 +1734,7 @@ function setRenderParentInProjectedNodes( const lastNodeToProject = (node as LProjectionNode).data.tail; while (nodeToProject) { if (nodeToProject.dynamicLContainerNode) { - nodeToProject.dynamicLContainerNode.data.renderParent = renderParent; + nodeToProject.dynamicLContainerNode.data[RENDER_PARENT] = renderParent; } nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent; } @@ -1770,26 +1749,27 @@ function setRenderParentInProjectedNodes( /** * Refreshes components by entering the component view and processing its bindings, queries, etc. * - * @param directiveIndex - * @param elementIndex + * @param directiveIndex Directive index in LViewData[DIRECTIVES] + * @param elementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET) */ export function componentRefresh(directiveIndex: number, elementIndex: number): void { ngDevMode && assertDataInRange(elementIndex); - const element = data ![elementIndex] as LElementNode; + const element = viewData[elementIndex] as LElementNode; ngDevMode && assertNodeType(element, TNodeType.Element); - ngDevMode && assertDefined(element.data, `Component's host node should have an LView attached.`); + ngDevMode && + assertDefined(element.data, `Component's host node should have an LViewData attached.`); const hostView = element.data !; // Only attached CheckAlways components or attached, dirty OnPush components should be checked - if (viewAttached(hostView) && hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { + if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { ngDevMode && assertDataInRange(directiveIndex, directives !); detectChangesInternal(hostView, element, getDirectiveInstance(directives ![directiveIndex])); } } /** Returns a boolean for whether the view is attached */ -function viewAttached(view: LView): boolean { - return (view.flags & LViewFlags.Attached) === LViewFlags.Attached; +export function viewAttached(view: LViewData): boolean { + return (view[FLAGS] & LViewFlags.Attached) === LViewFlags.Attached; } /** @@ -1821,7 +1801,7 @@ export function projectionDef( distributedNodes[i] = []; } - const componentNode: LElementNode = findComponentHost(currentView); + const componentNode: LElementNode = findComponentHost(viewData); let componentChild: LNode|null = getChildLNode(componentNode); while (componentChild !== null) { @@ -1838,8 +1818,8 @@ export function projectionDef( componentChild = getNextLNode(componentChild); } - ngDevMode && assertDataNext(index); - data[index] = distributedNodes; + ngDevMode && assertDataNext(index + HEADER_OFFSET); + store(index, distributedNodes); } /** @@ -1889,9 +1869,10 @@ export function projection( isParent = false; // re-distribution of projectable nodes is memorized on a component's view level - const componentNode = findComponentHost(currentView); - const componentLView = componentNode.data !; - const nodesForSelector = componentLView.data ![localIndex][selectorIndex]; + const componentNode = findComponentHost(viewData); + const componentLView = componentNode.data as LViewData; + const distributedNodes = loadInternal(localIndex, componentLView) as Array; + const nodesForSelector = distributedNodes[selectorIndex]; // build the linked list of projected nodes: for (let i = 0; i < nodesForSelector.length; i++) { @@ -1909,7 +1890,7 @@ export function projection( } const currentParent = getParentLNode(node); - if (canInsertNativeNode(currentParent, currentView)) { + if (canInsertNativeNode(currentParent, viewData)) { ngDevMode && assertNodeType(currentParent, TNodeType.Element); // process each node in the list of projected nodes: let nodeToProject: LNode|null = node.data.head; @@ -1917,7 +1898,7 @@ export function projection( while (nodeToProject) { appendProjectedNode( nodeToProject as LTextNode | LElementNode | LContainerNode, currentParent as LElementNode, - currentView); + viewData); nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent; } } @@ -1926,15 +1907,15 @@ export function projection( /** * Given a current view, finds the nearest component's host (LElement). * - * @param lView LView for which we want a host element node + * @param lViewData LViewData for which we want a host element node * @returns The host node */ -function findComponentHost(lView: LView): LElementNode { - let viewRootLNode = lView.node; +function findComponentHost(lViewData: LViewData): LElementNode { + let viewRootLNode = lViewData[HOST_NODE]; while (viewRootLNode.tNode.type === TNodeType.View) { - ngDevMode && assertDefined(lView.parent, 'lView.parent'); - lView = lView.parent !; - viewRootLNode = lView.node; + ngDevMode && assertDefined(lViewData[PARENT], 'lViewData.parent'); + lViewData = lViewData[PARENT] !; + viewRootLNode = lViewData[HOST_NODE]; } ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element); @@ -1944,25 +1925,24 @@ function findComponentHost(lView: LView): LElementNode { } /** - * Adds a LView or a LContainer to the end of the current view tree. + * Adds LViewData or LContainer to the end of the current view tree. * * This structure will be used to traverse through nested views to remove listeners * and call onDestroy callbacks. * - * @param currentView The view where LView or LContainer should be added - * @param hostIndex Index of the view's host node in data[] - * @param state The LView or LContainer to add to the view tree + * @param currentView The view where LViewData or LContainer should be added + * @param adjustedHostIndex Index of the view's host node in LViewData[], adjusted for header + * @param state The LViewData or LContainer to add to the view tree * @returns The state passed in */ -export function addToViewTree( - currentView: LView, hostIndex: number, state: T): T { - // TODO(kara): move next and tail properties off of LView - if (currentView.tail) { - currentView.tail.next = state; +export function addToViewTree( + currentView: LViewData, adjustedHostIndex: number, state: T): T { + if (currentView[TAIL]) { + currentView[TAIL][NEXT] = state; } else if (firstTemplatePass) { - currentView.tView.childIndex = hostIndex; + tView.childIndex = adjustedHostIndex; } - currentView.tail = state; + currentView[TAIL] = state; return state; } @@ -1970,11 +1950,11 @@ export function addToViewTree( //// Change detection /////////////////////////////// -/** If node is an OnPush component, marks its LView dirty. */ +/** If node is an OnPush component, marks its LViewData dirty. */ export function markDirtyIfOnPush(node: LElementNode): void { // Because data flows down the component tree, ancestors do not need to be marked dirty - if (node.data && !(node.data.flags & LViewFlags.CheckAlways)) { - node.data.flags |= LViewFlags.Dirty; + if (node.data && !(node.data[FLAGS] & LViewFlags.CheckAlways)) { + node.data[FLAGS] |= LViewFlags.Dirty; } } @@ -1982,8 +1962,8 @@ export function markDirtyIfOnPush(node: LElementNode): void { * Wraps an event listener so its host view and its ancestor views will be marked dirty * whenever the event fires. Necessary to support OnPush components. */ -export function wrapListenerWithDirtyLogic(view: LView, listenerFn: (e?: any) => any): (e: Event) => - any { +export function wrapListenerWithDirtyLogic( + view: LViewData, listenerFn: (e?: any) => any): (e: Event) => any { return function(e: any) { markViewDirty(view); return listenerFn(e); @@ -1995,7 +1975,7 @@ export function wrapListenerWithDirtyLogic(view: LView, listenerFn: (e?: any) => * whenever the event fires. Also wraps with preventDefault behavior. */ export function wrapListenerWithDirtyAndDefault( - view: LView, listenerFn: (e?: any) => any): EventListener { + view: LViewData, listenerFn: (e?: any) => any): EventListener { return function wrapListenerIn_markViewDirty(e: Event) { markViewDirty(view); if (listenerFn(e) === false) { @@ -2007,17 +1987,16 @@ export function wrapListenerWithDirtyAndDefault( } /** Marks current view and all ancestors dirty */ -export function markViewDirty(view: LView): void { - let currentView: LView|null = view; +export function markViewDirty(view: LViewData): void { + let currentView: LViewData|null = view; - while (currentView.parent != null) { - currentView.flags |= LViewFlags.Dirty; - currentView = currentView.parent; + while (currentView ![PARENT] != null) { + currentView ![FLAGS] |= LViewFlags.Dirty; + currentView = currentView ![PARENT]; } - currentView.flags |= LViewFlags.Dirty; - - ngDevMode && assertDefined(currentView !.context, 'rootContext'); - scheduleTick(currentView !.context as RootContext); + currentView ![FLAGS] |= LViewFlags.Dirty; + ngDevMode && assertDefined(currentView ![CONTEXT], 'rootContext'); + scheduleTick(currentView ![CONTEXT] as RootContext); } @@ -2058,7 +2037,7 @@ export function scheduleTick(rootContext: RootContext) { */ export function tick(component: T): void { const rootView = getRootView(component); - const rootContext = rootView.context as RootContext; + const rootContext = rootView[CONTEXT] as RootContext; tickRootContext(rootContext); } @@ -2073,20 +2052,20 @@ function tickRootContext(rootContext: RootContext) { } /** - * Retrieve the root view from any component by walking the parent `LView` until - * reaching the root `LView`. + * Retrieve the root view from any component by walking the parent `LViewData` until + * reaching the root `LViewData`. * * @param component any component */ -export function getRootView(component: any): LView { +export function getRootView(component: any): LViewData { ngDevMode && assertDefined(component, 'component'); const lElementNode = _getComponentHostLElementNode(component); - let lView = lElementNode.view; - while (lView.parent) { - lView = lView.parent; + let lViewData = lElementNode.view; + while (lViewData[PARENT]) { + lViewData = lViewData[PARENT]; } - return lView; + return lViewData; } /** @@ -2104,8 +2083,10 @@ export function getRootView(component: any): LView { */ export function detectChanges(component: T): void { const hostNode = _getComponentHostLElementNode(component); - ngDevMode && assertDefined(hostNode.data, 'Component host node should be attached to an LView'); - detectChangesInternal(hostNode.data as LView, hostNode, component); + ngDevMode && + assertDefined( + hostNode.data, 'Component host node should be attached to an LViewData instance.'); + detectChangesInternal(hostNode.data as LViewData, hostNode, component); } @@ -2125,9 +2106,10 @@ export function checkNoChanges(component: T): void { } /** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */ -export function detectChangesInternal(hostView: LView, hostNode: LElementNode, component: T) { +export function detectChangesInternal( + hostView: LViewData, hostNode: LElementNode, component: T) { const oldView = enterView(hostView, hostNode); - const template = hostView.tView.template !; + const template = hostView[TVIEW].template !; try { template(getRenderFlags(hostView), component); @@ -2178,12 +2160,12 @@ export const NO_CHANGE = {} as NO_CHANGE; */ function initBindings() { ngDevMode && assertEqual( - currentView.bindingIndex, -1, - 'Binding index should not yet be set ' + currentView.bindingIndex); - if (currentView.tView.bindingStartIndex === -1) { - currentView.tView.bindingStartIndex = data.length; + viewData[BINDING_INDEX], -1, + 'Binding index should not yet be set ' + viewData[BINDING_INDEX]); + if (tView.bindingStartIndex === -1) { + tView.bindingStartIndex = viewData.length; } - currentView.bindingIndex = currentView.tView.bindingStartIndex; + viewData[BINDING_INDEX] = tView.bindingStartIndex; } /** @@ -2216,8 +2198,8 @@ export function reserveSlots(numSlots: number) { // Init the slots with a unique `NO_CHANGE` value so that the first change is always detected // whether it happens or not during the first change detection pass - pure functions checks // might be skipped when short-circuited. - data.length += numSlots; - data.fill(NO_CHANGE, -numSlots); + viewData.length += numSlots; + viewData.fill(NO_CHANGE, -numSlots); // We need to initialize the binding in case a `pureFunctionX` kind of binding instruction is // called first in the update section. initBindings(); @@ -2231,8 +2213,8 @@ export function reserveSlots(numSlots: number) { * {@link reserveSlots} */ export function moveBindingIndexToReservedSlot(offset: number): number { - const currentSlot = currentView.bindingIndex; - currentView.bindingIndex = currentView.tView.bindingStartIndex - offset; + const currentSlot = viewData[BINDING_INDEX]; + viewData[BINDING_INDEX] = tView.bindingStartIndex - offset; return currentSlot; } @@ -2243,7 +2225,7 @@ export function moveBindingIndexToReservedSlot(offset: number): number { * been executed. */ export function restoreBindingIndex(index: number): void { - currentView.bindingIndex = index; + viewData[BINDING_INDEX] = index; } /** @@ -2385,16 +2367,22 @@ export function interpolation8( export function store(index: number, value: T): void { // 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 >= tData.length) { - tData[index] = null; + const adjustedIndex = index + HEADER_OFFSET; + if (adjustedIndex >= tView.data.length) { + tView.data[adjustedIndex] = null; } - data[index] = value; + viewData[adjustedIndex] = value; } -/** Retrieves a value from the `data`. */ +/** Retrieves a value from current `viewData`. */ export function load(index: number): T { - ngDevMode && assertDataInRange(index); - return data[index]; + return loadInternal(index, viewData); +} + +/** Retrieves a value from any `LViewData`. */ +export function loadInternal(index: number, arr: LViewData): T { + ngDevMode && assertDataInRange(index + HEADER_OFFSET, arr); + return arr[index + HEADER_OFFSET]; } /** Retrieves a value from the `directives` array. */ @@ -2406,26 +2394,26 @@ export function loadDirective(index: number): T { /** Gets the current binding value and increments the binding index. */ export function consumeBinding(): any { - ngDevMode && assertDataInRange(currentView.bindingIndex); + ngDevMode && assertDataInRange(viewData[BINDING_INDEX]); ngDevMode && assertNotEqual( - data[currentView.bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.'); - return data[currentView.bindingIndex++]; + viewData[viewData[BINDING_INDEX]], NO_CHANGE, 'Stored value should never be NO_CHANGE.'); + return viewData[viewData[BINDING_INDEX]++]; } /** Updates binding if changed, then returns whether it was updated. */ export function bindingUpdated(value: any): boolean { ngDevMode && assertNotEqual(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.'); - if (currentView.bindingIndex === -1) initBindings(); - - if (currentView.bindingIndex >= data.length) { - data[currentView.bindingIndex++] = value; - } else if (isDifferent(data[currentView.bindingIndex], value)) { - throwErrorIfNoChangesMode( - creationMode, checkNoChangesMode, data[currentView.bindingIndex], value); - data[currentView.bindingIndex++] = value; + if (viewData[BINDING_INDEX] === -1) initBindings(); + const bindingIndex = viewData[BINDING_INDEX]; + + if (bindingIndex >= viewData.length) { + viewData[viewData[BINDING_INDEX]++] = value; + } else if (isDifferent(viewData[bindingIndex], value)) { + throwErrorIfNoChangesMode(creationMode, checkNoChangesMode, viewData[bindingIndex], value); + viewData[viewData[BINDING_INDEX]++] = value; } else { - currentView.bindingIndex++; + viewData[BINDING_INDEX]++; return false; } return true; @@ -2450,7 +2438,7 @@ export function bindingUpdated4(exp1: any, exp2: any, exp3: any, exp4: any): boo } export function getTView(): TView { - return currentView.tView; + return tView; } export function getDirectiveInstance(instanceOrArray: T | [T]): T { @@ -2468,12 +2456,12 @@ function assertHasParent() { } function assertDataInRange(index: number, arr?: any[]) { - if (arr == null) arr = data; + if (arr == null) arr = viewData; assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index'); } function assertDataNext(index: number, arr?: any[]) { - if (arr == null) arr = data; + if (arr == null) arr = viewData; assertEqual( arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`); } @@ -2485,10 +2473,10 @@ function assertDataNext(index: number, arr?: any[]) { */ export function assertReservedSlotInitialized(slotOffset: number, numSlots: number) { if (firstTemplatePass) { - const startIndex = currentView.tView.bindingStartIndex - slotOffset; + const startIndex = tView.bindingStartIndex - slotOffset; for (let i = 0; i < numSlots; i++) { assertEqual( - data[startIndex + i], NO_CHANGE, + viewData[startIndex + i], NO_CHANGE, 'The reserved slots should be set to `NO_CHANGE` on first template pass'); } } diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 5ea41442d8533..0b2d9f0e7b112 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -6,72 +6,97 @@ * found in the LICENSE file at https://angular.io/license */ -import {LContainerNode, LElementNode, LViewNode} from './node'; +import {LElementNode, LViewNode} from './node'; import {LQueries} from './query'; -import {LView, TView} from './view'; +/** + * The state associated with an LContainerNode. + * + * This is an array so that its structure is closer to LViewData. This helps + * when traversing the view tree (which is a mix of containers and component + * views), so we can jump to viewOrContainer[NEXT] in the same way regardless + * of type. + */ +export type LContainer = [ + any | null, // next TODO: replace any with LViewData|LContainer + any | null, // parent + LQueries | null, // queries + number | null, // active index + LViewNode[], // views + LElementNode | null // render parent +]; +/** + * Below are constants for LContainer indices to help us look up LContainer members + * without having to remember the specific indices. + * Uglify will inline these when minifying so there shouldn't be a cost. + * + * 0: NEXT + * + * This allows us to jump from a container to a sibling container or component + * view with the same parent, so we can remove listeners efficiently. + * (The actual const is stored in view.ts because it's shared with LViewData). + * + * 1: PARENT + * + * Access to the parent view is necessary so we can propagate back + * up from inside a container to parent.next. + * (The actual const is stored in view.ts because it's shared with LViewData). + * + * 2: QUERIES + * + * Queries active for this container - all the views inserted to / removed from + * this container are reported to queries referenced here. + * (The actual const is stored in view.ts because it's shared with LViewData). + * + * 3: ACTIVE_INDEX + * + * The next active index in the views array to read or write to. This helps us + * keep track of where we are in the views array. + * In the case the LContainer is created for a ViewContainerRef, + * it is set to null to identify this scenario, as indices are "absolute" in that case, + * i.e. provided directly by the user of the ViewContainerRef API. + */ +const _ACTIVE_INDEX = 3; -/** The state associated with an LContainer */ -export interface LContainer { - /** - * The next active index in the views array to read or write to. This helps us - * keep track of where we are in the views array. - * In the case the LContainer is created for a ViewContainerRef, - * it is set to null to identify this scenario, as indices are "absolute" in that case, - * i.e. provided directly by the user of the ViewContainerRef API. - */ - nextIndex: number|null; - - /** - * This allows us to jump from a container to a sibling container or - * component view with the same parent, so we can remove listeners efficiently. - */ - next: LView|LContainer|null; - - /** - * Access to the parent view is necessary so we can propagate back - * up from inside a container to parent.next. - */ - parent: LView|null; - - /** - * A list of the container's currently active child views. Views will be inserted - * here as they are added and spliced from here when they are removed. We need - * to keep a record of current views so we know which views are already in the DOM - * (and don't need to be re-added) and so we can remove views from the DOM when they - * are no longer required. - */ - readonly views: LViewNode[]; +/** + * 4: VIEWS + * + * A list of the container's currently active child views. Views will be inserted + * here as they are added and spliced from here when they are removed. We need + * to keep a record of current views so we know which views are already in the DOM + * (and don't need to be re-added) and so we can remove views from the DOM when they + * are no longer required. + */ +const _VIEWS = 4; - /** - * Parent Element which will contain the location where all of the Views will be - * inserted into to. - * - * If `renderParent` is `null` it is headless. This means that it is contained - * in another `LViewNode` which in turn is contained in another `LContainerNode` and - * therefore it does not yet have its own parent. - * - * If `renderParent` is not `null` then it may be: - * - same as `LContainerNode.parent` in which case it is just a normal container. - * - different from `LContainerNode.parent` in which case it has been re-projected. - * In other words `LContainerNode.parent` is logical parent where as - * `LContainer.projectedParent` is render parent. - * - * When views are inserted into `LContainerNode` then `renderParent` is: - * - `null`, we are in `LViewNode` keep going up a hierarchy until actual - * `renderParent` is found. - * - not `null`, then use the `projectedParent.native` as the `RElement` to insert - * `LViewNode`s into. - */ - renderParent: LElementNode|null; +/** + * 5: RENDER_PARENT + * + * Parent Element which will contain the location where all of the Views will be + * inserted into to. + * + * If `renderParent` is `null` it is headless. This means that it is contained + * in another `LViewNode` which in turn is contained in another `LContainerNode` and + * therefore it does not yet have its own parent. + * + * If `renderParent` is not `null` then it may be: + * - same as `LContainerNode.parent` in which case it is just a normal container. + * - different from `LContainerNode.parent` in which case it has been re-projected. + * In other words `LContainerNode.parent` is logical parent where as + * `LContainer.projectedParent` is render parent. + * + * When views are inserted into `LContainerNode` then `renderParent` is: + * - `null`, we are in `LViewNode` keep going up a hierarchy until actual + * `renderParent` is found. + * - not `null`, then use the `projectedParent.native` as the `RElement` to insert + * `LViewNode`s into. + */ +const _RENDER_PARENT = 5; - /** - * Queries active for this container - all the views inserted to / removed from - * this container are reported to queries referenced here. - */ - queries: LQueries|null; -} +export const ACTIVE_INDEX = _ACTIVE_INDEX; +export const VIEWS = _VIEWS; +export const RENDER_PARENT = _RENDER_PARENT; // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index d4ac991adadd9..b295a49ef0e40 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -10,8 +10,8 @@ import {LContainer} from './container'; import {LInjector} from './injector'; import {LProjection} from './projection'; import {LQueries} from './query'; -import {RElement, RNode, RText} from './renderer'; -import {LView, TData, TView} from './view'; +import {RElement, RText} from './renderer'; +import {LViewData, TView} from './view'; @@ -72,7 +72,7 @@ export interface LNode { * If LContainerNode, then `data` contains LContainer. * If LProjectionNode, then `data` contains LProjection. */ - readonly data: LView|LContainer|LProjection|null; + readonly data: LViewData|LContainer|LProjection|null; /** @@ -80,7 +80,7 @@ export interface LNode { * * When the injector is walking up a tree, it needs access to the `directives` (part of view). */ - readonly view: LView; + readonly view: LViewData; /** The injector associated with this node. Necessary for DI. */ nodeInjector: LInjector|null; @@ -120,7 +120,7 @@ export interface LElementNode extends LNode { readonly native: RElement; /** If Component then data has LView (light DOM) */ - readonly data: LView|null; + readonly data: LViewData|null; } /** LNode representing a #text node. */ @@ -134,7 +134,7 @@ export interface LTextNode extends LNode { /** Abstract node which contains root nodes of a view. */ export interface LViewNode extends LNode { readonly native: null; - readonly data: LView; + readonly data: LViewData; dynamicLContainerNode: null; } diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 204c1e0960139..3ca763486dc0d 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -16,132 +16,138 @@ import {LQueries} from './query'; import {Renderer3} from './renderer'; +/** Size of LViewData's header. Necessary to adjust for it when setting slots. */ +const _HEADER_OFFSET = 14; /** - * `LView` stores all of the information needed to process the instructions as + * `LViewData` stores all of the information needed to process the instructions as * they are invoked from the template. Each embedded view and component view has its - * own `LView`. When processing a particular view, we set the `currentView` to that - * `LView`. When that view is done processing, the `currentView` is set back to - * whatever the original `currentView` was before (the parent `LView`). + * own `LViewData`. When processing a particular view, we set the `viewData` to that + * `LViewData`. When that view is done processing, the `viewData` is set back to + * whatever the original `viewData` was before (the parent `LViewData`). * * Keeping separate state for each view facilities view insertion / deletion, so we * don't have to edit the data array based on which views are present. */ -export interface LView { - /** Flags for this view (see LViewFlags for definition of each bit). */ - flags: LViewFlags; +export type LViewData = [ + any | LContainer | null, // next TODO: find a way to replace any with LViewData + any | LContainer | null, // parent + LQueries | null, // queries + TView, // tView + LViewFlags, // flags + LViewNode | LElementNode, // hostNode + number, // bindingIndex + any[] | null, // directives + any[] | null, // cleanupInstances + {} | RootContext | null, // context + Injector | null, // injector + Renderer3, // renderer + Sanitizer | null, // sanitizer + any | LContainer | null // tail +]; + +// Below are constants for LViewData indices to help us look up LViewData members +// without having to remember the specific indices. +// Uglify will inline these when minifying so there shouldn't be a cost. - /** - * The parent view is needed when we exit the view and must restore the previous - * `LView`. Without this, the render method would have to keep a stack of - * views as it is recursively rendering templates. - */ - readonly parent: LView|null; +/** + * + * The next sibling LViewData or LContainer. + * + * Allows us to propagate between sibling view states that aren't in the same + * container. Embedded views already have a node.next, but it is only set for + * views in the same container. We need a way to link component views and views + * across containers as well. + */ +const _NEXT = 0; - /** - * Pointer to the `LViewNode` or `LElementNode` which represents the root of the view. - * - * If `LViewNode`, this is an embedded view of a container. We need this to be able to - * efficiently find the `LViewNode` when inserting the view into an anchor. - * - * If `LElementNode`, this is the LView of a component. - */ - // TODO(kara): Remove when we have parent/child on TNodes - readonly node: LViewNode|LElementNode; +/** + * The parent view is needed when we exit the view and must restore the previous + * `LViewData`. Without this, the render method would have to keep a stack of + * views as it is recursively rendering templates. + */ +const _PARENT = 1; - /** Renderer to be used for this view. */ - readonly renderer: Renderer3; +/** Queries active for this view - nodes from a view are reported to those queries. */ +const _QUERIES = 2; - /** - * The binding index we should access next. - * - * This is stored so that bindings can continue where they left off - * if a view is left midway through processing bindings (e.g. if there is - * a setter that creates an embedded view, like in ngIf). - */ - bindingIndex: number; +/** + * 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). + */ +const _TVIEW = 3; - /** - * When a view is destroyed, listeners need to be released and outputs need to be - * unsubscribed. This context array stores both listener functions wrapped with - * their context and output subscription instances for a particular view. - * - * These change per LView instance, so they cannot be stored on TView. Instead, - * TView.cleanup saves an index to the necessary context in this array. - */ - // TODO: collapse into data[] - cleanupInstances: any[]|null; +/** See LViewFlags */ +const _FLAGS = 4; - /** - * The last LView or LContainer beneath this LView in the hierarchy. - * - * The tail allows us to quickly add a new state to the end of the view list - * without having to propagate starting from the first child. - */ - tail: LView|LContainer|null; +/** + * Pointer to the `LViewNode` or `LElementNode` which represents the root of the view. + * + * If `LViewNode`, this is an embedded view of a container. We need this to be able to + * efficiently find the `LViewNode` when inserting the view into an anchor. + * + * If `LElementNode`, this is the LView of a component. + */ +// TODO(kara): Replace with index +const _HOST_NODE = 5; - /** - * The next sibling LView or LContainer. - * - * Allows us to propagate between sibling view states that aren't in the same - * container. Embedded views already have a node.next, but it is only set for - * views in the same container. We need a way to link component views and views - * across containers as well. - */ - next: LView|LContainer|null; +/** + * The binding index we should access next. + * + * This is stored so that bindings can continue where they left off + * if a view is left midway through processing bindings (e.g. if there is + * a setter that creates an embedded view, like in ngIf). + */ +const _BINDING_INDEX = 6; - /** - * This array stores all element/text/container nodes created inside this view - * and their bindings. Stored as an array rather than a linked list so we can - * look up nodes directly in the case of forward declaration or bindings - * (e.g. E(1)). - * - * All bindings for a given view are stored in the order in which they - * appear in the template, starting with `bindingStartIndex`. - * We use `bindingIndex` to internally keep track of which binding - * is currently active. - */ - readonly data: any[]; +/** + * An array of directive instances in the current view. + * + * These must be stored separately from LNodes because their presence is + * unknown at compile-time and thus space cannot be reserved in data[]. + */ +// TODO: flatten into LViewData[] +const _DIRECTIVES = 7; - /** - * An array of directive instances in the current view. - * - * These must be stored separately from LNodes because their presence is - * unknown at compile-time and thus space cannot be reserved in data[]. - */ - directives: any[]|null; +/** + * When a view is destroyed, listeners need to be released and outputs need to be + * unsubscribed. This context array stores both listener functions wrapped with + * their context and output subscription instances for a particular view. + * + * These change per LView instance, so they cannot be stored on TView. Instead, + * TView.cleanup saves an index to the necessary context in this array. + */ +// TODO: flatten into LViewData[] +const _CLEANUP = 8; - /** - * 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). - */ - tView: TView; +/** + * - For embedded views, the context with which to render the template. + * - For root view of the root component the context contains change detection data. + * - `null` otherwise. + */ +const _CONTEXT = 9; - /** - * - For embedded views, the context with which to render the template. - * - For root view of the root component the context contains change detection data. - * - `null` otherwise. - */ - context: {}|RootContext|null; +/** An optional Module Injector to be used as fall back after Element Injectors are consulted. */ +const _INJECTOR = 10; - /** - * Queries active for this view - nodes from a view are reported to those queries - */ - queries: LQueries|null; +/** Renderer to be used for this view. */ +const _RENDERER = 11; - /** - * An optional Module Injector to be used as fall back after Element Injectors are consulted. - */ - injector: Injector|null; +/** An optional custom sanitizer. */ +const _SANITIZER = 12; - /** - * An optional custom sanitizer - */ - sanitizer: Sanitizer|null; -} +/** + * The last LViewData or LContainer beneath this LViewData in the hierarchy. + * + * The tail allows us to quickly add a new state to the end of the view list + * without having to propagate starting from the first child. + */ +// TODO: replace with global +const _TAIL = 13; -/** Flags associated with an LView (saved in LView.flags) */ + +/** Flags associated with an LView (saved in LViewData[FLAGS]) */ export const enum LViewFlags { /** * Whether or not the view is in creationMode. @@ -175,14 +181,6 @@ export const enum LViewFlags { Destroyed = 0b100000, } -/** Interface necessary to work with view tree traversal */ -export interface LViewOrLContainer { - next: LView|LContainer|null; - views?: LViewNode[]; - tView?: TView; - parent: LView|null; -} - /** * The static data for an LView (shared between all templates of a * given type). @@ -388,7 +386,7 @@ export interface TView { * refreshed when the current view has finished its check. * * Even indices: Directive indices - * Odd indices: Element indices + * Odd indices: Element indices (adjusted for LViewData header offset) */ components: number[]|null; @@ -397,6 +395,9 @@ export interface TView { * * Even indices: Directive indices * Odd indices: Element indices + * + * Element indices are NOT adjusted for LViewData header offset because + * they will be fed into instructions that expect the raw index (e.g. elementProperty) */ hostBindings: number[]|null; } @@ -447,6 +448,22 @@ export type TData = (TNode | PipeDef| null)[]; /** Type for TView.currentMatches */ export type CurrentMatchesList = [DirectiveDef, (string | number | null)]; +export const HEADER_OFFSET = _HEADER_OFFSET; +export const TVIEW = _TVIEW; +export const FLAGS = _FLAGS; +export const NEXT = _NEXT; +export const TAIL = _TAIL; +export const PARENT = _PARENT; +export const HOST_NODE = _HOST_NODE; +export const BINDING_INDEX = _BINDING_INDEX; +export const DIRECTIVES = _DIRECTIVES; +export const CLEANUP = _CLEANUP; +export const CONTEXT = _CONTEXT; +export const QUERIES = _QUERIES; +export const INJECTOR = _INJECTOR; +export const RENDERER = _RENDERER; +export const SANITIZER = _SANITIZER; + // 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/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 716565fc716b1..c695f43457731 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -6,13 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertDefined} from './assert'; import {callHooks} from './hooks'; -import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; -import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; +import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; +import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeType, 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 {HookData, LView, LViewFlags, LViewOrLContainer, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; +import {CLEANUP, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeType} from './node_assert'; import {stringify} from './util'; @@ -69,17 +68,17 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem export function getNextLNode(node: LNode): LNode|null { // View nodes don't have TNodes, so their next must be retrieved through their LView. if (node.tNode.type === TNodeType.View) { - const lView = node.data as LView; - return lView.next ? (lView.next as LView).node : null; + const viewData = node.data as LViewData; + return viewData[NEXT] ? (viewData[NEXT] as LViewData)[HOST_NODE] : null; } - return node.tNode.next ? node.view.data[node.tNode.next !.index] : null; + return node.tNode.next ? node.view[node.tNode.next !.index] : null; } /** Retrieves the first child of a given node */ export function getChildLNode(node: LNode): LNode|null { if (node.tNode.child) { - const view = node.tNode.type === TNodeType.View ? node.data as LView : node.view; - return view.data[node.tNode.child.index]; + const viewData = node.tNode.type === TNodeType.View ? node.data as LViewData : node.view; + return viewData[node.tNode.child.index]; } return null; } @@ -92,7 +91,7 @@ export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNo export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null { if (node.tNode.index === -1) return null; const parent = node.tNode.parent; - return parent ? node.view.data[parent.index] : node.view.node; + return parent ? node.view[parent.index] : node.view[HOST_NODE]; } /** @@ -213,10 +212,10 @@ function walkLNodeTree( lContainerNode.dynamicLContainerNode.data : lContainerNode.data; if (renderParentNode) { - childContainerData.renderParent = renderParentNode; + childContainerData[RENDER_PARENT] = renderParentNode; } nextNode = - childContainerData.views.length ? getChildLNode(childContainerData.views[0]) : null; + childContainerData[VIEWS].length ? getChildLNode(childContainerData[VIEWS][0]) : null; } else if (node.tNode.type === TNodeType.Projection) { // For Projection look at the first projected node nextNode = (node as LProjectionNode).data.head; @@ -256,11 +255,11 @@ export function addRemoveViewFromContainer( beforeNode?: RNode | null): void { ngDevMode && assertNodeType(container, TNodeType.Container); ngDevMode && assertNodeType(rootNode, TNodeType.View); - const parentNode = container.data.renderParent; + const parentNode = container.data[RENDER_PARENT]; const parent = parentNode ? parentNode.native : null; if (parent) { let node: LNode|null = getChildLNode(rootNode); - const renderer = container.view.renderer; + const renderer = container.view[RENDERER]; walkLNodeTree( node, rootNode, insertMode ? WalkLNodeTreeAction.Insert : WalkLNodeTreeAction.Detach, renderer, parentNode, beforeNode); @@ -280,38 +279,35 @@ export function addRemoveViewFromContainer( * * @param rootView The view to destroy */ -export function destroyViewTree(rootView: LView): void { +export function destroyViewTree(rootView: LViewData): void { // If the view has no children, we can clean it up and return early. - if (rootView.tView.childIndex === -1) { + if (rootView[TVIEW].childIndex === -1) { return cleanUpView(rootView); } - let viewOrContainer: LViewOrLContainer|null = getLViewChild(rootView); + let viewOrContainer: LViewData|LContainer|null = getLViewChild(rootView); while (viewOrContainer) { - let next: LViewOrLContainer|null = null; - - if (viewOrContainer.views && viewOrContainer.views.length) { - next = viewOrContainer.views[0].data; - } else if (viewOrContainer.tView && viewOrContainer.tView.childIndex > -1) { - next = getLViewChild(viewOrContainer as LView); - } else if (viewOrContainer.next) { - // Only move to the side and clean if operating below rootView - - // otherwise we would start cleaning up sibling views of the rootView. - cleanUpView(viewOrContainer); - next = viewOrContainer.next; + let next: LViewData|LContainer|null = null; + + if (viewOrContainer.length >= HEADER_OFFSET) { + // If LViewData, traverse down to child. + const view = viewOrContainer as LViewData; + if (view[TVIEW].childIndex > -1) next = getLViewChild(view); + } else { + // If container, traverse down to its first LViewData. + const container = viewOrContainer as LContainer; + if (container[VIEWS].length) next = container[VIEWS][0].data; } if (next == null) { - // If the viewOrContainer is the rootView and next is null it means that we are dealing - // with a root view that doesn't have children. We didn't descend into child views - // so no need to go back up the views tree. - while (viewOrContainer && !viewOrContainer !.next && viewOrContainer !== rootView) { + // Only clean up view when moving to the side or up, as destroy hooks + // should be called in order from the bottom up. + while (viewOrContainer && !viewOrContainer ![NEXT] && viewOrContainer !== rootView) { cleanUpView(viewOrContainer); viewOrContainer = getParentState(viewOrContainer, rootView); } cleanUpView(viewOrContainer || rootView); - - next = viewOrContainer && viewOrContainer.next; + next = viewOrContainer && viewOrContainer ![NEXT]; } viewOrContainer = next; } @@ -333,31 +329,31 @@ export function destroyViewTree(rootView: LView): void { export function insertView( container: LContainerNode, viewNode: LViewNode, index: number): LViewNode { const state = container.data; - const views = state.views; + const views = state[VIEWS]; if (index > 0) { // This is a new view, we need to add it to the children. - views[index - 1].data.next = viewNode.data as LView; + views[index - 1].data[NEXT] = viewNode.data as LViewData; } if (index < views.length) { - viewNode.data.next = views[index].data; + viewNode.data[NEXT] = views[index].data; views.splice(index, 0, viewNode); } else { views.push(viewNode); - viewNode.data.next = null; + viewNode.data[NEXT] = null; } // Notify query that a new view has been added const lView = viewNode.data; - if (lView.queries) { - lView.queries.insertView(index); + if (lView[QUERIES]) { + lView[QUERIES] !.insertView(index); } // If the container's renderParent is null, we know that it is a root node of its own parent view // and we should wait until that parent processes its nodes (otherwise, we will insert this view's // nodes twice - once now and once when its parent inserts its views). - if (container.data.renderParent !== null) { + if (container.data[RENDER_PARENT] !== null) { let beforeNode = findNextRNodeSibling(viewNode, container); if (!beforeNode) { @@ -371,7 +367,7 @@ export function insertView( } // Sets the attached flag - viewNode.data.flags |= LViewFlags.Attached; + viewNode.data[FLAGS] |= LViewFlags.Attached; return viewNode; } @@ -387,20 +383,20 @@ export function insertView( * @returns The detached view */ export function detachView(container: LContainerNode, removeIndex: number): LViewNode { - const views = container.data.views; + const views = container.data[VIEWS]; const viewNode = views[removeIndex]; if (removeIndex > 0) { - views[removeIndex - 1].data.next = viewNode.data.next as LView; + views[removeIndex - 1].data[NEXT] = viewNode.data[NEXT] as LViewData; } views.splice(removeIndex, 1); addRemoveViewFromContainer(container, viewNode, false); // Notify query that view has been removed const removedLview = viewNode.data; - if (removedLview.queries) { - removedLview.queries.removeView(removeIndex); + if (removedLview[QUERIES]) { + removedLview[QUERIES] !.removeView(removeIndex); } // Unsets the attached flag - viewNode.data.flags &= ~LViewFlags.Attached; + viewNode.data[FLAGS] &= ~LViewFlags.Attached; return viewNode; } @@ -412,17 +408,17 @@ export function detachView(container: LContainerNode, removeIndex: number): LVie * @returns The removed view */ export function removeView(container: LContainerNode, removeIndex: number): LViewNode { - const viewNode = container.data.views[removeIndex]; + const viewNode = container.data[VIEWS][removeIndex]; detachView(container, removeIndex); destroyLView(viewNode.data); return viewNode; } -/** Gets the child of the given LView */ -export function getLViewChild(view: LView): LView|LContainer|null { - if (view.tView.childIndex === -1) return null; +/** Gets the child of the given LViewData */ +export function getLViewChild(viewData: LViewData): LViewData|LContainer|null { + if (viewData[TVIEW].childIndex === -1) return null; - const hostNode: LElementNode|LContainerNode = view.data[view.tView.childIndex]; + const hostNode: LElementNode|LContainerNode = viewData[viewData[TVIEW].childIndex]; return hostNode.data ? hostNode.data : (hostNode.dynamicLContainerNode as LContainerNode).data; } @@ -433,14 +429,14 @@ export function getLViewChild(view: LView): LView|LContainer|null { * * @param view The view to be destroyed. */ -export function destroyLView(view: LView) { - const renderer = view.renderer; +export function destroyLView(view: LViewData) { + const renderer = view[RENDERER]; if (isProceduralRenderer(renderer) && renderer.destroyNode) { - walkLNodeTree(view.node, view.node, WalkLNodeTreeAction.Destroy, renderer); + walkLNodeTree(view[HOST_NODE], view[HOST_NODE], WalkLNodeTreeAction.Destroy, renderer); } destroyViewTree(view); // Sets the destroyed flag - view.flags |= LViewFlags.Destroyed; + view[FLAGS] |= LViewFlags.Destroyed; } /** @@ -455,76 +451,77 @@ export function destroyLView(view: LView) { * @param rootView The rootView, so we don't propagate too far up the view tree * @returns The correct parent LViewOrLContainer */ -export function getParentState(state: LViewOrLContainer, rootView: LView): LViewOrLContainer|null { +export function getParentState(state: LViewData | LContainer, rootView: LViewData): LViewData| + LContainer|null { let node; - if ((node = (state as LView) !.node) && node.tNode.type === TNodeType.View) { + if ((node = (state as LViewData) ![HOST_NODE]) && node.tNode.type === TNodeType.View) { // if it's an embedded view, the state needs to go up to the container, in case the // container has a next return getParentLNode(node) !.data as any; } else { // otherwise, use parent view for containers or component views - return state.parent === rootView ? null : state.parent; + return state[PARENT] === rootView ? null : state[PARENT]; } } /** * Removes all listeners and call all onDestroys in a given view. * - * @param view The LView to clean up + * @param view The LViewData to clean up */ -function cleanUpView(viewOrContainer: LViewOrLContainer): void { - if ((viewOrContainer as LView).tView) { - const view = viewOrContainer as LView; +function cleanUpView(viewOrContainer: LViewData | LContainer): void { + if ((viewOrContainer as LViewData)[TVIEW]) { + const view = viewOrContainer as LViewData; removeListeners(view); executeOnDestroys(view); executePipeOnDestroys(view); // For component views only, the local renderer is destroyed as clean up time. - if (view.tView.id === -1 && isProceduralRenderer(view.renderer)) { + if (view[TVIEW].id === -1 && isProceduralRenderer(view[RENDERER])) { ngDevMode && ngDevMode.rendererDestroy++; - view.renderer.destroy(); + (view[RENDERER] as ProceduralRenderer3).destroy(); } } } /** Removes listeners and unsubscribes from output subscriptions */ -function removeListeners(view: LView): void { - const cleanup = view.tView.cleanup !; +function removeListeners(viewData: LViewData): void { + const cleanup = viewData[TVIEW].cleanup !; if (cleanup != null) { for (let i = 0; i < cleanup.length - 1; i += 2) { if (typeof cleanup[i] === 'string') { // This is a listener with the native renderer - const native = view.data[cleanup[i + 1]].native; - const listener = view.cleanupInstances ![cleanup[i + 2]]; + const native = viewData[cleanup[i + 1]].native; + const listener = viewData[CLEANUP] ![cleanup[i + 2]]; native.removeEventListener(cleanup[i], listener, cleanup[i + 3]); i += 2; } else if (typeof cleanup[i] === 'number') { // This is a listener with renderer2 (cleanup fn can be found by index) - const cleanupFn = view.cleanupInstances ![cleanup[i]]; + const cleanupFn = viewData[CLEANUP] ![cleanup[i]]; cleanupFn(); } else { // This is a cleanup function that is grouped with the index of its context - const context = view.cleanupInstances ![cleanup[i + 1]]; + const context = viewData[CLEANUP] ![cleanup[i + 1]]; cleanup[i].call(context); } } - view.cleanupInstances = null; + viewData[CLEANUP] = null; } } /** Calls onDestroy hooks for this view */ -function executeOnDestroys(view: LView): void { - const tView = view.tView; +function executeOnDestroys(view: LViewData): void { + const tView = view[TVIEW]; let destroyHooks: HookData|null; if (tView != null && (destroyHooks = tView.destroyHooks) != null) { - callHooks(view.directives !, destroyHooks); + callHooks(view[DIRECTIVES] !, destroyHooks); } } /** Calls pipe destroy hooks for this view */ -function executePipeOnDestroys(view: LView): void { - const pipeDestroyHooks = view.tView && view.tView.pipeDestroyHooks; +function executePipeOnDestroys(viewData: LViewData): void { + const pipeDestroyHooks = viewData[TVIEW] && viewData[TVIEW].pipeDestroyHooks; if (pipeDestroyHooks) { - callHooks(view.data !, pipeDestroyHooks); + callHooks(viewData !, pipeDestroyHooks); } } @@ -545,7 +542,7 @@ function executePipeOnDestroys(view: LView): void { * @param currentView The LView being processed * @return boolean Whether the child element should be inserted. */ -export function canInsertNativeNode(parent: LNode, currentView: LView): boolean { +export function canInsertNativeNode(parent: LNode, currentView: LViewData): boolean { const parentIsElement = parent.tNode.type === TNodeType.Element; return parentIsElement && @@ -562,10 +559,10 @@ export function canInsertNativeNode(parent: LNode, currentView: LView): boolean * @param currentView The current LView * @returns Whether or not the child was appended */ -export function appendChild(parent: LNode, child: RNode | null, currentView: LView): boolean { +export function appendChild(parent: LNode, child: RNode | null, currentView: LViewData): boolean { if (child !== null && canInsertNativeNode(parent, currentView)) { // We only add element if not in View or not projected. - const renderer = currentView.renderer; + const renderer = currentView[RENDERER]; isProceduralRenderer(renderer) ? renderer.appendChild(parent.native !as RElement, child) : parent.native !.appendChild(child); return true; @@ -583,7 +580,7 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi */ export function appendProjectedNode( node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode, - currentView: LView): void { + currentView: LViewData): void { if (node.tNode.type !== TNodeType.Container) { appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView); } else { @@ -593,13 +590,13 @@ export function appendProjectedNode( // and can't be re-projected (as not content of any component). // Assignee the final projection location in those cases. const lContainer = (node as LContainerNode).data; - lContainer.renderParent = currentParent; - const views = lContainer.views; + lContainer[RENDER_PARENT] = currentParent; + const views = lContainer[VIEWS]; for (let i = 0; i < views.length; i++) { addRemoveViewFromContainer(node as LContainerNode, views[i], true, null); } } if (node.dynamicLContainerNode) { - node.dynamicLContainerNode.data.renderParent = currentParent; + node.dynamicLContainerNode.data[RENDER_PARENT] = currentParent; } } diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index 7f736721f7552..2bd2ef7cfedd2 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -10,6 +10,7 @@ import {PipeTransform} from '../change_detection/pipe_transform'; import {getTView, load, store} from './instructions'; import {PipeDef, PipeDefList} from './interfaces/definition'; +import {HEADER_OFFSET} from './interfaces/view'; import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunctionV} from './pure_function'; /** @@ -22,15 +23,17 @@ import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunction export function pipe(index: number, pipeName: string): any { const tView = getTView(); let pipeDef: PipeDef; + const adjustedIndex = index + HEADER_OFFSET; if (tView.firstTemplatePass) { pipeDef = getPipeDef(pipeName, tView.pipeRegistry); - tView.data[index] = pipeDef; + tView.data[adjustedIndex] = pipeDef; if (pipeDef.onDestroy) { - (tView.pipeDestroyHooks || (tView.pipeDestroyHooks = [])).push(index, pipeDef.onDestroy); + (tView.pipeDestroyHooks || (tView.pipeDestroyHooks = [ + ])).push(adjustedIndex, pipeDef.onDestroy); } } else { - pipeDef = tView.data[index] as PipeDef; + pipeDef = tView.data[adjustedIndex] as PipeDef; } const pipeInstance = pipeDef.factory(); @@ -148,5 +151,5 @@ export function pipeBindV(index: number, slotOffset: number, values: any[]): any } function isPure(index: number): boolean { - return (>getTView().data[index]).pure; + return (>getTView().data[index + HEADER_OFFSET]).pure; } diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index c04f3f48c8de9..f2cdec8a1552b 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -22,6 +22,7 @@ import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfac import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, TNode, TNodeFlags, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {LQueries, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; +import {DIRECTIVES, TVIEW} from './interfaces/view'; import {flatten} from './util'; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4; @@ -222,7 +223,7 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { * @returns Index of a found directive or null when none found. */ function getIdxOfMatchingDirective(node: LNode, type: Type): number|null { - const defs = node.view.tView.directives !; + const defs = node.view[TVIEW].directives !; const flags = node.tNode.flags; const count = flags & TNodeFlags.DirectiveCountMask; const start = flags >> TNodeFlags.DirectiveStartingIndexShift; @@ -244,7 +245,7 @@ function readFromNodeInjector( } else { const matchingIdx = getIdxOfMatchingDirective(node, read as Type); if (matchingIdx !== null) { - return node.view.directives ![matchingIdx]; + return node.view[DIRECTIVES] ![matchingIdx]; } } return null; @@ -416,7 +417,7 @@ export function query( const queryList = new QueryList(); const queries = getCurrentQueries(LQueries_); queries.track(queryList, predicate, descend, read); - storeCleanupWithContext(undefined, queryList, queryList.destroy); + storeCleanupWithContext(null, queryList, queryList.destroy); if (memoryIndex != null) { store(memoryIndex, queryList); } diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 17a7beb5a8016..432ab7b4ff566 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -11,10 +11,10 @@ import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detec import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref'; import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref'; -import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn} from './instructions'; +import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn, viewAttached} from './instructions'; import {ComponentTemplate} from './interfaces/definition'; import {LViewNode} from './interfaces/node'; -import {LView, LViewFlags} from './interfaces/view'; +import {FLAGS, LViewData, LViewFlags} from './interfaces/view'; import {destroyLView} from './node_manipulation'; // Needed due to tsickle downleveling where multiple `implements` with classes creates @@ -29,16 +29,16 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int context: T; rootNodes: any[]; - constructor(protected _view: LView, context: T|null) { this.context = context !; } + constructor(protected _view: LViewData, context: T|null) { this.context = context !; } /** @internal */ - _setComponentContext(view: LView, context: T) { + _setComponentContext(view: LViewData, context: T) { this._view = view; this.context = context; } get destroyed(): boolean { - return (this._view.flags & LViewFlags.Destroyed) === LViewFlags.Destroyed; + return (this._view[FLAGS] & LViewFlags.Destroyed) === LViewFlags.Destroyed; } destroy(): void { destroyLView(this._view); } @@ -132,7 +132,7 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int * } * ``` */ - detach(): void { this._view.flags &= ~LViewFlags.Attached; } + detach(): void { this._view[FLAGS] &= ~LViewFlags.Attached; } /** * Re-attaches a view to the change detection tree. @@ -189,7 +189,7 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int * } * ``` */ - reattach(): void { this._view.flags |= LViewFlags.Attached; } + reattach(): void { this._view[FLAGS] |= LViewFlags.Attached; } /** * Checks the view and its children. @@ -240,8 +240,7 @@ export class EmbeddedViewRef extends ViewRef { } destroy(): void { - if (this._viewContainerRef && - (this._view.flags & LViewFlags.Attached) === LViewFlags.Attached) { + if (this._viewContainerRef && viewAttached(this._view)) { this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)); this._viewContainerRef = null; } diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index c1c0de6f38024..66b81ef1ce6dd 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -1,31 +1,73 @@ [ + { + "name": "ACTIVE_INDEX" + }, + { + "name": "BINDING_INDEX" + }, { "name": "CLEAN_PROMISE" }, + { + "name": "CONTEXT" + }, + { + "name": "DIRECTIVES" + }, { "name": "EMPTY$2" }, { "name": "EMPTY_RENDERER_TYPE_ID" }, + { + "name": "FLAGS" + }, + { + "name": "HEADER_OFFSET" + }, + { + "name": "HOST_NODE" + }, { "name": "HelloWorld" }, + { + "name": "INJECTOR$1" + }, { "name": "INeedToExistEvenThoughIAmNotNeeded" }, + { + "name": "NEXT" + }, { "name": "NG_HOST_SYMBOL" }, { "name": "NG_PROJECT_AS_ATTR_NAME" }, + { + "name": "QUERIES" + }, + { + "name": "RENDERER" + }, { "name": "ROOT_DIRECTIVE_INDICES" }, + { + "name": "SANITIZER" + }, + { + "name": "TVIEW" + }, { "name": "UNDEFINED_RENDERER_TYPE_ID" }, + { + "name": "VIEWS" + }, { "name": "_renderCompCount" }, @@ -53,9 +95,6 @@ { "name": "createLNodeObject" }, - { - "name": "createLView" - }, { "name": "createRootContext" }, @@ -69,7 +108,7 @@ "name": "createTextNode" }, { - "name": "currentView" + "name": "createViewData" }, { "name": "defineComponent" @@ -131,9 +170,6 @@ { "name": "invertObject" }, - { - "name": "isLContainer" - }, { "name": "isProceduralRenderer" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 865432ac911c7..69a9de935db70 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -1,16 +1,31 @@ [ + { + "name": "ACTIVE_INDEX" + }, + { + "name": "BINDING_INDEX" + }, { "name": "BLOOM_SIZE" }, { "name": "CIRCULAR$2" }, + { + "name": "CLEANUP" + }, { "name": "CLEAN_PROMISE" }, + { + "name": "CONTEXT" + }, { "name": "CommonModule" }, + { + "name": "DIRECTIVES" + }, { "name": "DefaultIterableDiffer" }, @@ -29,12 +44,27 @@ { "name": "EmbeddedViewRef$1" }, + { + "name": "FLAGS" + }, + { + "name": "HEADER_OFFSET" + }, + { + "name": "HOST_NODE" + }, + { + "name": "INJECTOR$1" + }, { "name": "IterableChangeRecord_" }, { "name": "IterableDiffers" }, + { + "name": "NEXT" + }, { "name": "NG_ELEMENT_ID" }, @@ -65,15 +95,36 @@ { "name": "PARAMETERS" }, + { + "name": "PARENT" + }, + { + "name": "QUERIES" + }, + { + "name": "RENDERER" + }, + { + "name": "RENDER_PARENT" + }, { "name": "ROOT_DIRECTIVE_INDICES" }, { "name": "RecordViewTuple" }, + { + "name": "SANITIZER" + }, { "name": "SkipSelf" }, + { + "name": "TAIL" + }, + { + "name": "TVIEW" + }, { "name": "TemplateRef$1" }, @@ -92,6 +143,9 @@ { "name": "UNDEFINED_RENDERER_TYPE_ID" }, + { + "name": "VIEWS" + }, { "name": "ViewContainerRef$1" }, @@ -260,9 +314,6 @@ { "name": "createLNodeObject" }, - { - "name": "createLView" - }, { "name": "createOutput" }, @@ -279,7 +330,7 @@ "name": "createTextNode" }, { - "name": "currentView" + "name": "createViewData" }, { "name": "defineComponent" @@ -497,9 +548,6 @@ { "name": "isJsObject" }, - { - "name": "isLContainer" - }, { "name": "isListLikeIterable" }, @@ -527,6 +575,9 @@ { "name": "load" }, + { + "name": "loadInternal" + }, { "name": "locateHostElement" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index ea3d413d318ea..dc5c5e2b5a979 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -12,7 +12,7 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createTView, createViewData, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; @@ -1404,7 +1404,7 @@ describe('di', () => { describe('getOrCreateNodeInjector', () => { it('should handle initial undefined state', () => { const contentView = - createLView(null !, createTView(-1, null, null, null), null, LViewFlags.CheckAlways); + createViewData(null !, createTView(-1, null, null, null), null, LViewFlags.CheckAlways); const oldView = enterView(contentView, null !); try { const parent = createLNode(0, TNodeType.Element, null, null, null, null); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index fce716580d046..37e0729b919fc 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -9,8 +9,8 @@ import {RenderFlags} from '@angular/core/src/render3'; import {defineComponent, defineDirective} from '../../src/render3/index'; -import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; -import {LViewFlags} from '../../src/render3/interfaces/view'; +import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding,} from '../../src/render3/instructions'; +import {HEADER_OFFSET} from '../../src/render3/interfaces/view'; import {sanitizeUrl} from '../../src/sanitization/sanitization'; import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; @@ -849,8 +849,8 @@ describe('render3 integration test', () => { renderToHtml(Template, {condition: true}); const oldTemplateData = (Template as any).ngPrivateData; - const oldContainerData = (oldTemplateData as any).data[0]; - const oldElementData = oldContainerData.tViews[0][0]; + const oldContainerData = (oldTemplateData as any).data[HEADER_OFFSET]; + const oldElementData = oldContainerData.tViews[0][HEADER_OFFSET]; expect(oldContainerData).not.toBeNull(); expect(oldElementData).not.toBeNull(); @@ -858,8 +858,8 @@ describe('render3 integration test', () => { renderToHtml(Template, {condition: true}); const newTemplateData = (Template as any).ngPrivateData; - const newContainerData = (oldTemplateData as any).data[0]; - const newElementData = oldContainerData.tViews[0][0]; + const newContainerData = (oldTemplateData as any).data[HEADER_OFFSET]; + const newElementData = oldContainerData.tViews[0][HEADER_OFFSET]; expect(newTemplateData === oldTemplateData).toBe(true); expect(newContainerData === oldContainerData).toBe(true); expect(newElementData === oldElementData).toBe(true); From 51d90c533a51a2fc620fe08775b3708b3b82923d Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 8 Jun 2018 14:15:29 -0700 Subject: [PATCH 2/4] fixup! refactor(ivy): combine LView with data --- packages/core/src/render3/component.ts | 4 +-- packages/core/src/render3/component_ref.ts | 4 +-- packages/core/src/render3/instructions.ts | 32 +++++++++++-------- .../core/src/render3/interfaces/container.ts | 12 +++---- packages/core/src/render3/interfaces/query.ts | 2 +- packages/core/src/render3/interfaces/view.ts | 28 ++++++++-------- packages/core/test/render3/di_spec.ts | 4 +-- 7 files changed, 45 insertions(+), 41 deletions(-) diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 8150df4a9880b..5f275cd7cd5ca 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -14,7 +14,7 @@ import {Sanitizer} from '../sanitization/security'; import {assertComponentType, assertDefined} from './assert'; import {queueInitHooks, queueLifecycleHooks} from './hooks'; -import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings,} from './instructions'; +import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings,} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; import {LElementNode} from './interfaces/node'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; @@ -104,7 +104,7 @@ export function renderComponent( const hostNode = locateHostElement(rendererFactory, opts.host || componentTag); const rootContext = createRootContext(opts.scheduler || requestAnimationFrame.bind(window)); - const rootView: LViewData = createViewData( + const rootView: LViewData = createLViewData( rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(-1, null, null, null), rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index da04e9c126a89..059999677d5dd 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -18,7 +18,7 @@ import {Type} from '../type'; import {assertComponentType, assertDefined} from './assert'; import {createRootContext} from './component'; -import {baseDirectiveCreate, createTView, createViewData, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions'; +import {baseDirectiveCreate, createTView, createLViewData, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; import {LElementNode} from './interfaces/node'; import {RElement} from './interfaces/renderer'; @@ -94,7 +94,7 @@ export class ComponentFactory extends viewEngine_ComponentFactory { const rootContext: RootContext = ngModule !.injector.get(ROOT_CONTEXT); // Create the root view. Uses empty TView and ContentTemplate. - const rootView: LViewData = createViewData( + const rootView: LViewData = createLViewData( rendererFactory.createRenderer(hostNode, this.componentDef.rendererType), createTView(-1, null, null, null), null, this.componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 302acde821b63..3642b41bd1add 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -50,8 +50,12 @@ export type SanitizerFn = (value: any) => string; * Directive and element indices for top-level directive. * * Saved here to avoid re-instantiating an array on every change detection run. + * + * Note: Element is not actually stored at index 0 because of the LViewData + * header, but the host bindings function expects an index that is NOT adjusted + * because it will ultimately be fed to instructions like elementProperty. */ -export const _ROOT_DIRECTIVE_INDICES = [0, 0]; +const _ROOT_DIRECTIVE_INDICES = [0, 0]; /** * Token set in currentMatches while dependencies are being resolved. @@ -267,7 +271,7 @@ export function executeInitAndContentHooks(): void { } } -export function createViewData( +export function createLViewData( renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags, sanitizer?: Sanitizer | null): LViewData { return [ @@ -435,7 +439,7 @@ export function renderTemplate( const tView = getOrCreateTView(template, directives || null, pipes || null); host = createLNode( -1, TNodeType.Element, hostNode, null, null, - createViewData( + createLViewData( providedRendererFactory.createRenderer(null, null), tView, {}, LViewFlags.CheckAlways, sanitizer)); } @@ -468,7 +472,7 @@ export function renderEmbeddedTemplate( if (viewNode == null) { const lView = - createViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer()); + createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer()); if (queries) { lView[QUERIES] = queries.createView(); @@ -857,7 +861,7 @@ export function hostElement( resetApplicationState(); const node = createLNode( 0, TNodeType.Element, rNode, null, null, - createViewData( + createLViewData( renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); @@ -1343,7 +1347,7 @@ function addComponentLogic(directiveIndex: number, instance: T, def: Componen // accessed through their containers because they may be removed / re-added later. const componentView = addToViewTree( viewData, previousOrParentNode.tNode.index as number, - createViewData( + createLViewData( rendererFactory.createRenderer(previousOrParentNode.native as RElement, def.rendererType), tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer())); @@ -1655,7 +1659,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { enterView(viewNode.data, viewNode); } else { // When we create a new LView, we always reset the state of the instructions. - const newView = createViewData( + const newView = createLViewData( renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, LViewFlags.CheckAlways, getCurrentSanitizer()); @@ -1988,15 +1992,15 @@ export function wrapListenerWithDirtyAndDefault( /** Marks current view and all ancestors dirty */ export function markViewDirty(view: LViewData): void { - let currentView: LViewData|null = view; + let currentView: LViewData = view; - while (currentView ![PARENT] != null) { - currentView ![FLAGS] |= LViewFlags.Dirty; - currentView = currentView ![PARENT]; + while (currentView[PARENT] != null) { + currentView[FLAGS] |= LViewFlags.Dirty; + currentView = currentView[PARENT]; } - currentView ![FLAGS] |= LViewFlags.Dirty; - ngDevMode && assertDefined(currentView ![CONTEXT], 'rootContext'); - scheduleTick(currentView ![CONTEXT] as RootContext); + currentView[FLAGS] |= LViewFlags.Dirty; + ngDevMode && assertDefined(currentView[CONTEXT], 'rootContext'); + scheduleTick(currentView[CONTEXT] as RootContext); } diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 0b2d9f0e7b112..e4120d46640ae 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -18,12 +18,12 @@ import {LQueries} from './query'; * of type. */ export type LContainer = [ - any | null, // next TODO: replace any with LViewData|LContainer - any | null, // parent - LQueries | null, // queries - number | null, // active index - LViewNode[], // views - LElementNode | null // render parent + /** LViewData | LContainer */ any | null, // next + /** LViewData | LContainer */ any | null, // parent + LQueries | null, // queries + number | null, // active index + LViewNode[], // views + LElementNode | null // render parent ]; /** diff --git a/packages/core/src/render3/interfaces/query.ts b/packages/core/src/render3/interfaces/query.ts index 57822a0815145..93fe026799cf0 100644 --- a/packages/core/src/render3/interfaces/query.ts +++ b/packages/core/src/render3/interfaces/query.ts @@ -13,7 +13,7 @@ import {LNode} from './node'; /** Used for tracking queries (e.g. ViewChild, ContentChild). */ export interface LQueries { /** - * Used to ask queries if those should be cloned to the child element. + * Used to ask querieis if those should be cloned to the child element. * * For example in the case of deep queries the `child()` returns * queries for the child node. In case of shallow queries it returns diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 3ca763486dc0d..0efe6821f3d06 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -30,20 +30,20 @@ const _HEADER_OFFSET = 14; * don't have to edit the data array based on which views are present. */ export type LViewData = [ - any | LContainer | null, // next TODO: find a way to replace any with LViewData - any | LContainer | null, // parent - LQueries | null, // queries - TView, // tView - LViewFlags, // flags - LViewNode | LElementNode, // hostNode - number, // bindingIndex - any[] | null, // directives - any[] | null, // cleanupInstances - {} | RootContext | null, // context - Injector | null, // injector - Renderer3, // renderer - Sanitizer | null, // sanitizer - any | LContainer | null // tail + /** LViewData */ any | LContainer | null, // next + /** LViewData */ any | LContainer | null, // parent + LQueries | null, // queries + TView, // tView + LViewFlags, // flags + LViewNode | LElementNode, // hostNode + number, // bindingIndex + any[] | null, // directives + any[] | null, // cleanupInstances + {} | RootContext | null, // context + Injector | null, // injector + Renderer3, // renderer + Sanitizer | null, // sanitizer + /** LViewData */ any | LContainer | null // tail ]; // Below are constants for LViewData indices to help us look up LViewData members diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index dc5c5e2b5a979..07791626f53eb 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -12,7 +12,7 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createTView, createViewData, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createTView, createLViewData, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; @@ -1404,7 +1404,7 @@ describe('di', () => { describe('getOrCreateNodeInjector', () => { it('should handle initial undefined state', () => { const contentView = - createViewData(null !, createTView(-1, null, null, null), null, LViewFlags.CheckAlways); + createLViewData(null !, createTView(-1, null, null, null), null, LViewFlags.CheckAlways); const oldView = enterView(contentView, null !); try { const parent = createLNode(0, TNodeType.Element, null, null, null, null); From da311acb74360cc5aa26caf7c1f853b31c3fe503 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 8 Jun 2018 16:24:06 -0700 Subject: [PATCH 3/4] fixup! refactor(ivy): combine LView with data --- packages/core/src/render3/component_ref.ts | 2 +- packages/core/src/render3/instructions.ts | 40 +-- .../core/src/render3/interfaces/container.ts | 147 ++++++----- packages/core/src/render3/interfaces/view.ts | 239 +++++++++--------- .../hello_world/bundle.golden_symbols.json | 15 +- .../bundling/todo/bundle.golden_symbols.json | 9 +- packages/core/test/render3/di_spec.ts | 2 +- 7 files changed, 234 insertions(+), 220 deletions(-) diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 059999677d5dd..a903ca309c2de 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -18,7 +18,7 @@ import {Type} from '../type'; import {assertComponentType, assertDefined} from './assert'; import {createRootContext} from './component'; -import {baseDirectiveCreate, createTView, createLViewData, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions'; +import {baseDirectiveCreate, createLViewData, createTView, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; import {LElementNode} from './interfaces/node'; import {RElement} from './interfaces/renderer'; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 3642b41bd1add..9472465599ca4 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -57,6 +57,15 @@ export type SanitizerFn = (value: any) => string; */ const _ROOT_DIRECTIVE_INDICES = [0, 0]; +/** + * TView.data needs to fill the same number of slots as the LViewData header + * so the indices of nodes are consistent between LViewData and TView.data. + * + * It's much faster to keep a blueprint of the pre-filled array and slice it + * than it is to create a new array and fill it each time a TView is created. + */ +const HEADER_FILLER = new Array(HEADER_OFFSET).fill(null); + /** * Token set in currentMatches while dependencies are being resolved. * @@ -275,10 +284,10 @@ export function createLViewData( renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags, sanitizer?: Sanitizer | null): LViewData { return [ - null, // next + tView, // tView viewData, // parent + null, // next null, // queries - tView, // tView flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit, // flags null !, // hostNode -1, // bindingIndex @@ -779,9 +788,9 @@ export function createTView( id: viewIndex, template: template, node: null !, - data: new Array(HEADER_OFFSET).fill(null), // Fill in to match HEADER_OFFSET in LViewData - childIndex: -1, // Children set in addToViewTree(), if any - bindingStartIndex: -1, // Set in initBindings() + data: HEADER_FILLER.slice(), // Fill in to match HEADER_OFFSET in LViewData + childIndex: -1, // Children set in addToViewTree(), if any + bindingStartIndex: -1, // Set in initBindings() directives: null, firstTemplatePass: true, initHooks: null, @@ -1485,10 +1494,10 @@ export function createLContainer( parentLNode: LNode, currentView: LViewData, isForViewContainerRef?: boolean): LContainer { ngDevMode && assertDefined(parentLNode, 'containers should have a parent'); return [ - null, // next + isForViewContainerRef ? null : 0, // active index currentView, // parent + null, // next null, // queries - isForViewContainerRef ? null : 0, // active index [], // views canInsertNativeNode(parentLNode, currentView) ? parentLNode as LElementNode : null // renderParent @@ -1717,7 +1726,7 @@ export function embeddedViewEnd(): void { lContainer[ACTIVE_INDEX] !++; } - leaveView(viewData[PARENT]); + leaveView(viewData[PARENT] !); ngDevMode && assertEqual(isParent, false, 'isParent'); ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); } @@ -1754,11 +1763,11 @@ function setRenderParentInProjectedNodes( * Refreshes components by entering the component view and processing its bindings, queries, etc. * * @param directiveIndex Directive index in LViewData[DIRECTIVES] - * @param elementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET) + * @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET) */ -export function componentRefresh(directiveIndex: number, elementIndex: number): void { - ngDevMode && assertDataInRange(elementIndex); - const element = viewData[elementIndex] as LElementNode; +export function componentRefresh(directiveIndex: number, adjustedElementIndex: number): void { + ngDevMode && assertDataInRange(adjustedElementIndex); + const element = viewData[adjustedElementIndex] as LElementNode; ngDevMode && assertNodeType(element, TNodeType.Element); ngDevMode && assertDefined(element.data, `Component's host node should have an LViewData attached.`); @@ -1916,6 +1925,7 @@ export function projection( */ function findComponentHost(lViewData: LViewData): LElementNode { let viewRootLNode = lViewData[HOST_NODE]; + while (viewRootLNode.tNode.type === TNodeType.View) { ngDevMode && assertDefined(lViewData[PARENT], 'lViewData.parent'); lViewData = lViewData[PARENT] !; @@ -1942,7 +1952,7 @@ function findComponentHost(lViewData: LViewData): LElementNode { export function addToViewTree( currentView: LViewData, adjustedHostIndex: number, state: T): T { if (currentView[TAIL]) { - currentView[TAIL][NEXT] = state; + currentView[TAIL] ![NEXT] = state; } else if (firstTemplatePass) { tView.childIndex = adjustedHostIndex; } @@ -1996,7 +2006,7 @@ export function markViewDirty(view: LViewData): void { while (currentView[PARENT] != null) { currentView[FLAGS] |= LViewFlags.Dirty; - currentView = currentView[PARENT]; + currentView = currentView[PARENT] !; } currentView[FLAGS] |= LViewFlags.Dirty; ngDevMode && assertDefined(currentView[CONTEXT], 'rootContext'); @@ -2067,7 +2077,7 @@ export function getRootView(component: any): LViewData { const lElementNode = _getComponentHostLElementNode(component); let lViewData = lElementNode.view; while (lViewData[PARENT]) { - lViewData = lViewData[PARENT]; + lViewData = lViewData[PARENT] !; } return lViewData; } diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index e4120d46640ae..cc1267004810d 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -8,6 +8,22 @@ import {LElementNode, LViewNode} from './node'; import {LQueries} from './query'; +import {LViewData, NEXT, PARENT, QUERIES} from './view'; + +/** + * Below are constants for LContainer indices to help us look up LContainer members + * without having to remember the specific indices. + * Uglify will inline these when minifying so there shouldn't be a cost. + */ +const _ACTIVE_INDEX = 0; +// PARENT, NEXT, and QUERIES are indices 1, 2, and 3. +// As we already have these constants in LViewData, we don't need to re-create them. +const _VIEWS = 4; +const _RENDER_PARENT = 5; + +export const ACTIVE_INDEX = _ACTIVE_INDEX; +export const VIEWS = _VIEWS; +export const RENDER_PARENT = _RENDER_PARENT; /** * The state associated with an LContainerNode. @@ -17,86 +33,65 @@ import {LQueries} from './query'; * views), so we can jump to viewOrContainer[NEXT] in the same way regardless * of type. */ -export type LContainer = [ - /** LViewData | LContainer */ any | null, // next - /** LViewData | LContainer */ any | null, // parent - LQueries | null, // queries - number | null, // active index - LViewNode[], // views - LElementNode | null // render parent -]; +export interface LContainer extends Array { + /** + * The next active index in the views array to read or write to. This helps us + * keep track of where we are in the views array. + * In the case the LContainer is created for a ViewContainerRef, + * it is set to null to identify this scenario, as indices are "absolute" in that case, + * i.e. provided directly by the user of the ViewContainerRef API. + */ + [ACTIVE_INDEX]: number|null; -/** - * Below are constants for LContainer indices to help us look up LContainer members - * without having to remember the specific indices. - * Uglify will inline these when minifying so there shouldn't be a cost. - * - * 0: NEXT - * - * This allows us to jump from a container to a sibling container or component - * view with the same parent, so we can remove listeners efficiently. - * (The actual const is stored in view.ts because it's shared with LViewData). - * - * 1: PARENT - * - * Access to the parent view is necessary so we can propagate back - * up from inside a container to parent.next. - * (The actual const is stored in view.ts because it's shared with LViewData). - * - * 2: QUERIES - * - * Queries active for this container - all the views inserted to / removed from - * this container are reported to queries referenced here. - * (The actual const is stored in view.ts because it's shared with LViewData). - * - * 3: ACTIVE_INDEX - * - * The next active index in the views array to read or write to. This helps us - * keep track of where we are in the views array. - * In the case the LContainer is created for a ViewContainerRef, - * it is set to null to identify this scenario, as indices are "absolute" in that case, - * i.e. provided directly by the user of the ViewContainerRef API. - */ -const _ACTIVE_INDEX = 3; + /** + * Access to the parent view is necessary so we can propagate back + * up from inside a container to parent[NEXT]. + */ + [PARENT]: LViewData|null; -/** - * 4: VIEWS - * - * A list of the container's currently active child views. Views will be inserted - * here as they are added and spliced from here when they are removed. We need - * to keep a record of current views so we know which views are already in the DOM - * (and don't need to be re-added) and so we can remove views from the DOM when they - * are no longer required. - */ -const _VIEWS = 4; + /** + * This allows us to jump from a container to a sibling container or component + * view with the same parent, so we can remove listeners efficiently. + */ + [NEXT]: LViewData|LContainer|null; -/** - * 5: RENDER_PARENT - * - * Parent Element which will contain the location where all of the Views will be - * inserted into to. - * - * If `renderParent` is `null` it is headless. This means that it is contained - * in another `LViewNode` which in turn is contained in another `LContainerNode` and - * therefore it does not yet have its own parent. - * - * If `renderParent` is not `null` then it may be: - * - same as `LContainerNode.parent` in which case it is just a normal container. - * - different from `LContainerNode.parent` in which case it has been re-projected. - * In other words `LContainerNode.parent` is logical parent where as - * `LContainer.projectedParent` is render parent. - * - * When views are inserted into `LContainerNode` then `renderParent` is: - * - `null`, we are in `LViewNode` keep going up a hierarchy until actual - * `renderParent` is found. - * - not `null`, then use the `projectedParent.native` as the `RElement` to insert - * `LViewNode`s into. - */ -const _RENDER_PARENT = 5; + /** + * Queries active for this container - all the views inserted to / removed from + * this container are reported to queries referenced here. + */ + [QUERIES]: LQueries|null; -export const ACTIVE_INDEX = _ACTIVE_INDEX; -export const VIEWS = _VIEWS; -export const RENDER_PARENT = _RENDER_PARENT; + /** + * A list of the container's currently active child views. Views will be inserted + * here as they are added and spliced from here when they are removed. We need + * to keep a record of current views so we know which views are already in the DOM + * (and don't need to be re-added) and so we can remove views from the DOM when they + * are no longer required. + */ + [VIEWS]: LViewNode[]; + + /** + * Parent Element which will contain the location where all of the Views will be + * inserted into to. + * + * If `renderParent` is `null` it is headless. This means that it is contained + * in another `LViewNode` which in turn is contained in another `LContainerNode` and + * therefore it does not yet have its own parent. + * + * If `renderParent` is not `null` then it may be: + * - same as `LContainerNode.parent` in which case it is just a normal container. + * - different from `LContainerNode.parent` in which case it has been re-projected. + * In other words `LContainerNode.parent` is logical parent where as + * `LContainer.projectedParent` is render parent. + * + * When views are inserted into `LContainerNode` then `renderParent` is: + * - `null`, we are in `LViewNode` keep going up a hierarchy until actual + * `renderParent` is found. + * - not `null`, then use the `projectedParent.native` as the `RElement` to insert + * `LViewNode`s into. + */ + [RENDER_PARENT]: LElementNode|null; +} // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 0efe6821f3d06..5d076ae317807 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -15,9 +15,42 @@ import {LElementNode, LViewNode, TNode} from './node'; import {LQueries} from './query'; import {Renderer3} from './renderer'; - /** Size of LViewData's header. Necessary to adjust for it when setting slots. */ const _HEADER_OFFSET = 14; +export const HEADER_OFFSET = _HEADER_OFFSET; + +// Below are constants for LViewData indices to help us look up LViewData members +// without having to remember the specific indices. +// Uglify will inline these when minifying so there shouldn't be a cost. +const _TVIEW = 0; +const _PARENT = 1; +const _NEXT = 2; +const _QUERIES = 3; +const _FLAGS = 4; +const _HOST_NODE = 5; +const _BINDING_INDEX = 6; +const _DIRECTIVES = 7; +const _CLEANUP = 8; +const _CONTEXT = 9; +const _INJECTOR = 10; +const _RENDERER = 11; +const _SANITIZER = 12; +const _TAIL = 13; + +export const TVIEW = _TVIEW; +export const PARENT = _PARENT; +export const NEXT = _NEXT; +export const QUERIES = _QUERIES; +export const FLAGS = _FLAGS; +export const HOST_NODE = _HOST_NODE; +export const BINDING_INDEX = _BINDING_INDEX; +export const DIRECTIVES = _DIRECTIVES; +export const CLEANUP = _CLEANUP; +export const CONTEXT = _CONTEXT; +export const INJECTOR = _INJECTOR; +export const RENDERER = _RENDERER; +export const SANITIZER = _SANITIZER; +export const TAIL = _TAIL; /** * `LViewData` stores all of the information needed to process the instructions as @@ -29,123 +62,103 @@ const _HEADER_OFFSET = 14; * Keeping separate state for each view facilities view insertion / deletion, so we * don't have to edit the data array based on which views are present. */ -export type LViewData = [ - /** LViewData */ any | LContainer | null, // next - /** LViewData */ any | LContainer | null, // parent - LQueries | null, // queries - TView, // tView - LViewFlags, // flags - LViewNode | LElementNode, // hostNode - number, // bindingIndex - any[] | null, // directives - any[] | null, // cleanupInstances - {} | RootContext | null, // context - Injector | null, // injector - Renderer3, // renderer - Sanitizer | null, // sanitizer - /** LViewData */ any | LContainer | null // tail -]; - -// Below are constants for LViewData indices to help us look up LViewData members -// without having to remember the specific indices. -// Uglify will inline these when minifying so there shouldn't be a cost. - -/** - * - * The next sibling LViewData or LContainer. - * - * Allows us to propagate between sibling view states that aren't in the same - * container. Embedded views already have a node.next, but it is only set for - * views in the same container. We need a way to link component views and views - * across containers as well. - */ -const _NEXT = 0; - -/** - * The parent view is needed when we exit the view and must restore the previous - * `LViewData`. Without this, the render method would have to keep a stack of - * views as it is recursively rendering templates. - */ -const _PARENT = 1; +export interface LViewData extends Array { + /** + * 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). + */ + [TVIEW]: TView; -/** Queries active for this view - nodes from a view are reported to those queries. */ -const _QUERIES = 2; + /** + * The parent view is needed when we exit the view and must restore the previous + * `LViewData`. Without this, the render method would have to keep a stack of + * views as it is recursively rendering templates. + */ + [PARENT]: LViewData|null; -/** - * 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). - */ -const _TVIEW = 3; + /** + * + * The next sibling LViewData or LContainer. + * + * Allows us to propagate between sibling view states that aren't in the same + * container. Embedded views already have a node.next, but it is only set for + * views in the same container. We need a way to link component views and views + * across containers as well. + */ + [NEXT]: LViewData|LContainer|null; -/** See LViewFlags */ -const _FLAGS = 4; + /** Queries active for this view - nodes from a view are reported to those queries. */ + [QUERIES]: LQueries|null; -/** - * Pointer to the `LViewNode` or `LElementNode` which represents the root of the view. - * - * If `LViewNode`, this is an embedded view of a container. We need this to be able to - * efficiently find the `LViewNode` when inserting the view into an anchor. - * - * If `LElementNode`, this is the LView of a component. - */ -// TODO(kara): Replace with index -const _HOST_NODE = 5; + /** Flags for this view. See LViewFlags for more info. */ + [FLAGS]: LViewFlags; -/** - * The binding index we should access next. - * - * This is stored so that bindings can continue where they left off - * if a view is left midway through processing bindings (e.g. if there is - * a setter that creates an embedded view, like in ngIf). - */ -const _BINDING_INDEX = 6; + /** + * Pointer to the `LViewNode` or `LElementNode` which represents the root of the view. + * + * If `LViewNode`, this is an embedded view of a container. We need this to be able to + * efficiently find the `LViewNode` when inserting the view into an anchor. + * + * If `LElementNode`, this is the LView of a component. + */ + // TODO(kara): Replace with index + [HOST_NODE]: LViewNode|LElementNode; -/** - * An array of directive instances in the current view. - * - * These must be stored separately from LNodes because their presence is - * unknown at compile-time and thus space cannot be reserved in data[]. - */ -// TODO: flatten into LViewData[] -const _DIRECTIVES = 7; + /** + * The binding index we should access next. + * + * This is stored so that bindings can continue where they left off + * if a view is left midway through processing bindings (e.g. if there is + * a setter that creates an embedded view, like in ngIf). + */ + [BINDING_INDEX]: number; -/** - * When a view is destroyed, listeners need to be released and outputs need to be - * unsubscribed. This context array stores both listener functions wrapped with - * their context and output subscription instances for a particular view. - * - * These change per LView instance, so they cannot be stored on TView. Instead, - * TView.cleanup saves an index to the necessary context in this array. - */ -// TODO: flatten into LViewData[] -const _CLEANUP = 8; + /** + * An array of directive instances in the current view. + * + * These must be stored separately from LNodes because their presence is + * unknown at compile-time and thus space cannot be reserved in data[]. + */ + // TODO: flatten into LViewData[] + [DIRECTIVES]: any[]|null; -/** - * - For embedded views, the context with which to render the template. - * - For root view of the root component the context contains change detection data. - * - `null` otherwise. - */ -const _CONTEXT = 9; + /** + * When a view is destroyed, listeners need to be released and outputs need to be + * unsubscribed. This context array stores both listener functions wrapped with + * their context and output subscription instances for a particular view. + * + * These change per LView instance, so they cannot be stored on TView. Instead, + * TView.cleanup saves an index to the necessary context in this array. + */ + // TODO: flatten into LViewData[] + [CLEANUP]: any[]|null; -/** An optional Module Injector to be used as fall back after Element Injectors are consulted. */ -const _INJECTOR = 10; + /** + * - For embedded views, the context with which to render the template. + * - For root view of the root component the context contains change detection data. + * - `null` otherwise. + */ + [CONTEXT]: {}|RootContext|null; -/** Renderer to be used for this view. */ -const _RENDERER = 11; + /** An optional Module Injector to be used as fall back after Element Injectors are consulted. */ + [INJECTOR]: Injector|null; -/** An optional custom sanitizer. */ -const _SANITIZER = 12; + /** Renderer to be used for this view. */ + [RENDERER]: Renderer3; -/** - * The last LViewData or LContainer beneath this LViewData in the hierarchy. - * - * The tail allows us to quickly add a new state to the end of the view list - * without having to propagate starting from the first child. - */ -// TODO: replace with global -const _TAIL = 13; + /** An optional custom sanitizer. */ + [SANITIZER]: Sanitizer|null; + /** + * The last LViewData or LContainer beneath this LViewData in the hierarchy. + * + * The tail allows us to quickly add a new state to the end of the view list + * without having to propagate starting from the first child. + */ + // TODO: replace with global + [TAIL]: LViewData|LContainer|null; +} /** Flags associated with an LView (saved in LViewData[FLAGS]) */ export const enum LViewFlags { @@ -448,22 +461,6 @@ export type TData = (TNode | PipeDef| null)[]; /** Type for TView.currentMatches */ export type CurrentMatchesList = [DirectiveDef, (string | number | null)]; -export const HEADER_OFFSET = _HEADER_OFFSET; -export const TVIEW = _TVIEW; -export const FLAGS = _FLAGS; -export const NEXT = _NEXT; -export const TAIL = _TAIL; -export const PARENT = _PARENT; -export const HOST_NODE = _HOST_NODE; -export const BINDING_INDEX = _BINDING_INDEX; -export const DIRECTIVES = _DIRECTIVES; -export const CLEANUP = _CLEANUP; -export const CONTEXT = _CONTEXT; -export const QUERIES = _QUERIES; -export const INJECTOR = _INJECTOR; -export const RENDERER = _RENDERER; -export const SANITIZER = _SANITIZER; - // 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/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 66b81ef1ce6dd..6fc22af83ceda 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -23,6 +23,9 @@ { "name": "FLAGS" }, + { + "name": "HEADER_FILLER" + }, { "name": "HEADER_OFFSET" }, @@ -68,6 +71,12 @@ { "name": "VIEWS" }, + { + "name": "_CLEAN_PROMISE" + }, + { + "name": "_ROOT_DIRECTIVE_INDICES" + }, { "name": "_renderCompCount" }, @@ -95,6 +104,9 @@ { "name": "createLNodeObject" }, + { + "name": "createLViewData" + }, { "name": "createRootContext" }, @@ -107,9 +119,6 @@ { "name": "createTextNode" }, - { - "name": "createViewData" - }, { "name": "defineComponent" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 69a9de935db70..9d767df347c18 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -47,6 +47,9 @@ { "name": "FLAGS" }, + { + "name": "HEADER_FILLER" + }, { "name": "HEADER_OFFSET" }, @@ -314,6 +317,9 @@ { "name": "createLNodeObject" }, + { + "name": "createLViewData" + }, { "name": "createOutput" }, @@ -329,9 +335,6 @@ { "name": "createTextNode" }, - { - "name": "createViewData" - }, { "name": "defineComponent" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 07791626f53eb..66e0f67d092b9 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -12,7 +12,7 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createTView, createLViewData, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLViewData, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; From 4b298055219182fb41865e2cf4a5d485d77ad94b Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 8 Jun 2018 16:32:16 -0700 Subject: [PATCH 4/4] fixup! refactor(ivy): combine LView with data --- .../core/src/render3/interfaces/container.ts | 10 ++-- packages/core/src/render3/interfaces/view.ts | 46 ++++++------------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index cc1267004810d..402aa95afc3c7 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -15,15 +15,11 @@ import {LViewData, NEXT, PARENT, QUERIES} from './view'; * without having to remember the specific indices. * Uglify will inline these when minifying so there shouldn't be a cost. */ -const _ACTIVE_INDEX = 0; +export const ACTIVE_INDEX = 0; // PARENT, NEXT, and QUERIES are indices 1, 2, and 3. // As we already have these constants in LViewData, we don't need to re-create them. -const _VIEWS = 4; -const _RENDER_PARENT = 5; - -export const ACTIVE_INDEX = _ACTIVE_INDEX; -export const VIEWS = _VIEWS; -export const RENDER_PARENT = _RENDER_PARENT; +export const VIEWS = 4; +export const RENDER_PARENT = 5; /** * The state associated with an LContainerNode. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 5d076ae317807..cbedb714e8731 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -16,41 +16,25 @@ import {LQueries} from './query'; import {Renderer3} from './renderer'; /** Size of LViewData's header. Necessary to adjust for it when setting slots. */ -const _HEADER_OFFSET = 14; -export const HEADER_OFFSET = _HEADER_OFFSET; +export const HEADER_OFFSET = 14; // Below are constants for LViewData indices to help us look up LViewData members // without having to remember the specific indices. // Uglify will inline these when minifying so there shouldn't be a cost. -const _TVIEW = 0; -const _PARENT = 1; -const _NEXT = 2; -const _QUERIES = 3; -const _FLAGS = 4; -const _HOST_NODE = 5; -const _BINDING_INDEX = 6; -const _DIRECTIVES = 7; -const _CLEANUP = 8; -const _CONTEXT = 9; -const _INJECTOR = 10; -const _RENDERER = 11; -const _SANITIZER = 12; -const _TAIL = 13; - -export const TVIEW = _TVIEW; -export const PARENT = _PARENT; -export const NEXT = _NEXT; -export const QUERIES = _QUERIES; -export const FLAGS = _FLAGS; -export const HOST_NODE = _HOST_NODE; -export const BINDING_INDEX = _BINDING_INDEX; -export const DIRECTIVES = _DIRECTIVES; -export const CLEANUP = _CLEANUP; -export const CONTEXT = _CONTEXT; -export const INJECTOR = _INJECTOR; -export const RENDERER = _RENDERER; -export const SANITIZER = _SANITIZER; -export const TAIL = _TAIL; +export const TVIEW = 0; +export const PARENT = 1; +export const NEXT = 2; +export const QUERIES = 3; +export const FLAGS = 4; +export const HOST_NODE = 5; +export const BINDING_INDEX = 6; +export const DIRECTIVES = 7; +export const CLEANUP = 8; +export const CONTEXT = 9; +export const INJECTOR = 10; +export const RENDERER = 11; +export const SANITIZER = 12; +export const TAIL = 13; /** * `LViewData` stores all of the information needed to process the instructions as