diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 450fe9afcd..a37b93bca8 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -590,7 +590,7 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer lContainerNode.tNode = hostTNode.dynamicContainerNode; vcRefHost.dynamicLContainerNode = lContainerNode; - addToViewTree(vcRefHost.view, lContainer); + addToViewTree(vcRefHost.view, hostTNode.index as number, lContainer); di.viewContainerRef = new ViewContainerRef(lContainerNode); } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 523c605a68..cc6659c7e6 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -17,7 +17,7 @@ import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TDat 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} from './node_manipulation'; +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 {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; @@ -314,7 +314,6 @@ export function createLView( tView: tView, cleanup: null, renderer: renderer, - child: null, tail: null, next: null, bindingStartIndex: -1, @@ -802,6 +801,7 @@ export function createTView( return { node: null !, data: [], + childIndex: -1, // Children set in addToViewTree(), if any directives: null, firstTemplatePass: true, initHooks: null, @@ -1329,11 +1329,12 @@ function addComponentLogic(index: number, instance: T, def: ComponentDef): // 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, createLView( - -1, rendererFactory.createRenderer( - previousOrParentNode.native as RElement, def.rendererType), - tView, null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, - getCurrentSanitizer())); + currentView, previousOrParentNode.tNode.index as number, + createLView( + -1, + rendererFactory.createRenderer(previousOrParentNode.native as RElement, def.rendererType), + tView, null, 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). @@ -1512,7 +1513,7 @@ export function container( // Containers are added to the current view tree instead of their embedded views // because views can be removed and re-inserted. - addToViewTree(currentView, node.data); + addToViewTree(currentView, index, node.data); createDirectivesAndLocals(localRefs); isParent = false; @@ -1574,7 +1575,7 @@ export function containerRefreshEnd(): void { } function refreshDynamicChildren() { - for (let current = currentView.child; current !== null; current = current.next) { + 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)) { @@ -1921,11 +1922,18 @@ function findComponentHost(lView: LView): LElementNode { * 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 * @returns The state passed in */ -export function addToViewTree(currentView: LView, state: T): T { - currentView.tail ? (currentView.tail.next = state) : (currentView.child = state); +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; + } else if (firstTemplatePass) { + currentView.tView.childIndex = hostIndex; + } currentView.tail = state; return state; } diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index a234389f4a..b7c0c3819a 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -112,17 +112,6 @@ export interface LView { */ lifecycleStage: LifecycleStage; - /** - * The first LView or LContainer beneath this LView in the hierarchy. - * - * Necessary to store this so views can traverse through their nested views - * to remove listeners and call onDestroy callbacks. - * - * For embedded views, we store the LContainer rather than the first ViewState - * to avoid managing splicing when views are added/removed. - */ - child: LView|LContainer|null; - /** * The last LView or LContainer beneath this LView in the hierarchy. * @@ -222,8 +211,8 @@ export const enum LViewFlags { /** Interface necessary to work with view tree traversal */ export interface LViewOrLContainer { next: LView|LContainer|null; - child?: LView|LContainer|null; views?: LViewNode[]; + tView?: TView; parent: LView|null; } @@ -251,6 +240,18 @@ export interface TView { /** Static data equivalent of LView.data[]. Contains TNodes. */ data: TData; + /** + * Index of the host node of the first LView or LContainer beneath this LView in + * the hierarchy. + * + * Necessary to store this so views can traverse through their nested views + * to remove listeners and call onDestroy callbacks. + * + * For embedded views, we store the index of an LContainer's host rather than the first + * LView to avoid managing splicing when views are added/removed. + */ + childIndex: number; + /** * Selector matches for a node are temporarily cached on the TView so the * DI system can eagerly instantiate directives on the same node if they are diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 95633efbea..31f6df401a 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -261,19 +261,19 @@ export function addRemoveViewFromContainer( * @param rootView The view to destroy */ export function destroyViewTree(rootView: LView): void { - // A view to cleanup doesn't have children so we should not try to descend down the view tree. - if (!rootView.child) { + // If the view has no children, we can clean it up and return early. + if (rootView.tView.childIndex === -1) { return cleanUpView(rootView); } - let viewOrContainer: LViewOrLContainer|null = rootView.child; + let viewOrContainer: LViewOrLContainer|null = getLViewChild(rootView); while (viewOrContainer) { let next: LViewOrLContainer|null = null; if (viewOrContainer.views && viewOrContainer.views.length) { next = viewOrContainer.views[0].data; - } else if (viewOrContainer.child) { - next = viewOrContainer.child; + } 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. @@ -383,6 +383,15 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie return viewNode; } +/** Gets the child of the given LView */ +export function getLViewChild(view: LView): LView|LContainer|null { + if (view.tView.childIndex === -1) return null; + + const hostNode: LElementNode|LContainerNode = view.data[view.tView.childIndex]; + + return hostNode.data ? hostNode.data : (hostNode.dynamicLContainerNode as LContainerNode).data; +} + /** * Determines which LViewOrLContainer to jump to when traversing back up the * tree in destroyViewTree. 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 77abaff7ae..df06f6a31f 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -107,6 +107,9 @@ { "name": "getDirectiveInstance" }, + { + "name": "getLViewChild" + }, { "name": "getOrCreateTView" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 6b487a0142..fccb3b48e3 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -389,6 +389,9 @@ { "name": "getDirectiveInstance" }, + { + "name": "getLViewChild" + }, { "name": "getNextLNode" },