diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index d1196f92ca..0a23c5b424 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 {LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; -import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode} from './node_manipulation'; +import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode} 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'; @@ -344,7 +344,6 @@ export function createLNodeObject( native: native as any, view: currentView, parent: parent as any, - child: null, nodeInjector: parent ? parent.nodeInjector : null, data: state, queries: queries, @@ -414,15 +413,10 @@ export function createLNode( // Now link ourselves into the tree. if (isParent) { currentQueries = null; - if (previousOrParentNode.view === currentView || + if (previousOrParentNode.tNode.child == null && previousOrParentNode.view === currentView || previousOrParentNode.tNode.type === TNodeType.View) { // We are in the same view, which means we are adding content node to the parent View. - ngDevMode && assertNull( - previousOrParentNode.child, - `previousOrParentNode's child should not have been set.`); - previousOrParentNode.child = node; - } else { - // We are adding component view, so we don't link parent node child to this node. + previousOrParentNode.tNode.child = node.tNode; } } } @@ -1063,6 +1057,7 @@ export function createTNode( outputs: undefined, tViews: tViews, next: null, + child: null, dynamicContainerNode: null }; } @@ -1699,7 +1694,7 @@ export function embeddedViewEnd(): void { function setRenderParentInProjectedNodes( renderParent: LElementNode | null, viewNode: LViewNode): void { if (renderParent != null) { - let node: LNode|null = viewNode.child; + let node: LNode|null = getChildLNode(viewNode); while (node) { if (node.tNode.type === TNodeType.Projection) { let nodeToProject: LNode|null = (node as LProjectionNode).data.head; @@ -1776,7 +1771,7 @@ export function projectionDef( } const componentNode: LElementNode = findComponentHost(currentView); - let componentChild: LNode|null = componentNode.child; + let componentChild: LNode|null = getChildLNode(componentNode); while (componentChild !== null) { // execute selector matching logic if and only if: diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 4608572a15..0701f960eb 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -71,11 +71,6 @@ export interface LNode { */ readonly parent: LNode|null; - /** - * First child of the current node. - */ - child: LNode|null; - /** * If regular LElementNode, then `data` will be null. * If LElementNode with component, then `data` contains LView. @@ -130,8 +125,6 @@ export interface LElementNode extends LNode { /** The DOM element associated with this node. */ readonly native: RElement; - child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - /** If Component then data has LView (light DOM) */ readonly data: LView|null; @@ -143,7 +136,6 @@ export interface LElementNode extends LNode { export interface LTextNode extends LNode { /** The text node associated with this node. */ native: RText; - child: null; /** LTextNodes can be inside LElementNodes or inside LViewNodes. */ readonly parent: LElementNode|LViewNode; @@ -154,7 +146,6 @@ export interface LTextNode extends LNode { /** Abstract node which contains root nodes of a view. */ export interface LViewNode extends LNode { readonly native: null; - child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; /** LViewNodes can only be added to LContainerNodes. */ readonly parent: LContainerNode|null; @@ -173,7 +164,6 @@ export interface LContainerNode extends LNode { */ native: RElement|RText|null|undefined; readonly data: LContainer; - child: null; /** Containers can be added to elements or views. */ readonly parent: LElementNode|LViewNode|null; @@ -182,7 +172,6 @@ export interface LContainerNode extends LNode { export interface LProjectionNode extends LNode { readonly native: null; - child: null; readonly data: LProjection; @@ -310,6 +299,14 @@ export interface TNode { */ next: TNode|null; + /** + * First child of the current node. + * + * For component nodes, the child will always be a ContentChild (in same view). + * For embedded view nodes, the child will be in their child view. + */ + child: TNode|null; + /** * A pointer to a TContainerNode created by directives requesting ViewContainerRef */ @@ -317,10 +314,34 @@ export interface TNode { } /** Static data for an LElementNode */ -export interface TElementNode extends TNode { tViews: null; } +export interface TElementNode extends TNode { + child: TContainerNode|TElementNode|TProjectionNode|null; + tViews: null; +} + +/** Static data for an LTextNode */ +export interface TTextNode extends TNode { + child: null; + tViews: null; +} /** Static data for an LContainerNode */ -export interface TContainerNode extends TNode { tViews: TView|TView[]|null; } +export interface TContainerNode extends TNode { + child: null; + tViews: TView|TView[]|null; +} + +/** Static data for an LViewNode */ +export interface TViewNode extends TNode { + child: TContainerNode|TElementNode|TProjectionNode|null; + tViews: null; +} + +/** Static data for an LProjectionNode */ +export interface TProjectionNode extends TNode { + child: null; + tViews: null; +} /** * This mapping is necessary so we can set input properties and output listeners diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index b1a5b3fa17..3ae554dd92 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -75,6 +75,15 @@ export function getNextLNode(node: LNode): LNode|null { return node.tNode.next ? node.view.data[node.tNode.next !.index as number] : 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 as number]; + } + return null; +} + /** * Get the next node in the LNode tree, taking into account the place where a node is * projected (in the shadow DOM) rather than where it comes from (in the light DOM). @@ -140,13 +149,14 @@ function findFirstRNode(rootNode: LNode): RElement|RText|null { const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ? lContainerNode.dynamicLContainerNode.data : lContainerNode.data; - nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; + nextNode = + 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; } else { // Otherwise look at the first child - nextNode = (node as LViewNode).child; + nextNode = getChildLNode(node as LViewNode); } node = nextNode === null ? getNextOrParentSiblingNode(node, rootNode) : nextNode; @@ -183,7 +193,7 @@ export function addRemoveViewFromContainer( ngDevMode && assertNodeType(rootNode, TNodeType.View); const parentNode = container.data.renderParent; const parent = parentNode ? parentNode.native : null; - let node: LNode|null = rootNode.child; + let node: LNode|null = getChildLNode(rootNode); if (parent) { while (node) { let nextNode: LNode|null = null; @@ -203,11 +213,12 @@ export function addRemoveViewFromContainer( // propagating down into child views / containers and not child elements const childContainerData: LContainer = (node as LContainerNode).data; childContainerData.renderParent = parentNode; - nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; + nextNode = + childContainerData.views.length ? getChildLNode(childContainerData.views[0]) : null; } else if (node.tNode.type === TNodeType.Projection) { nextNode = (node as LProjectionNode).data.head; } else { - nextNode = (node as LViewNode).child; + nextNode = getChildLNode(node as LViewNode); } if (nextNode === null) { node = getNextOrParentSiblingNode(node, rootNode); diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 2a9c789e3d..7cdd8fb784 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -383,6 +383,9 @@ { "name": "generatePropertyAliases" }, + { + "name": "getChildLNode" + }, { "name": "getCurrentSanitizer" }, diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index 638d73b58c..d98944e264 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -22,6 +22,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): TNode { outputs: undefined, tViews: null, next: null, + child: null, dynamicContainerNode: null }; }