Skip to content

Commit 581f878

Browse files
committed
fix(core): correct container anchor collection order to match DOM layout
Historically, `collectNativeNodes` collected the container's anchor comment node (`LContainer[NATIVE]`) before descending into the views contained inside the `LContainer`. While this worked logically, it did not match the actual physical layout of the DOM tree, where dynamic view content is inserted before the container anchor. This discrepancy was particularly visible in projected `@content` blocks where the anchor comment ended up rendered at the beginning instead of the end of the content block. This commit refactors `collectNativeNodes` to push the anchor comment node at the very end of view collection inside `collectNativeNodesInLContainer`. For dynamic containers where `lContainer[NATIVE] !== lContainer[HOST]` (e.g., a `ViewContainerRef` injected on a `div` element), we still push the host element first before descending to preserve the physical DOM layout. Associated acceptance tests in `template_ref_spec.ts` are updated to match the physically correct DOM order.
1 parent e695379 commit 581f878

2 files changed

Lines changed: 19 additions & 27 deletions

File tree

packages/core/src/render3/collect_native_nodes.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,19 @@ export function collectNativeNodes(
5454

5555
const lNode = lView[tNode.index];
5656
if (lNode !== null) {
57-
result.push(unwrapRNode(lNode));
58-
}
57+
if (isLContainer(lNode)) {
58+
// If this is a dynamic container (ViewContainerRef on an element), the HOST element is a
59+
// distinct element node and must be pushed first (before the views are collected) to
60+
// preserve DOM order.
61+
if (lNode[NATIVE] !== lNode[HOST]) {
62+
result.push(unwrapRNode(lNode));
63+
}
5964

60-
// A given lNode can represent either a native node or a LContainer (when it is a host of a
61-
// ViewContainerRef). When we find a LContainer we need to descend into it to collect root nodes
62-
// from the views in this container.
63-
if (isLContainer(lNode)) {
64-
collectNativeNodesInLContainer(lNode, result);
65+
// Collect the root nodes from the views in this container.
66+
collectNativeNodesInLContainer(lNode, result);
67+
} else {
68+
result.push(unwrapRNode(lNode));
69+
}
6570
}
6671

6772
const tNodeType = tNode.type;
@@ -101,20 +106,7 @@ export function collectNativeNodesInLContainer(lContainer: LContainer, result: a
101106
}
102107
}
103108

104-
// When an LContainer is created, the anchor (comment) node is:
105-
// - (1) either reused in case of an ElementContainer (<ng-container>)
106-
// - (2) or a new comment node is created
107-
// In the first case, the anchor comment node would be added to the final
108-
// list by the code in the `collectNativeNodes` function
109-
// (see the `result.push(unwrapRNode(lNode))` line), but the second
110-
// case requires extra handling: the anchor node needs to be added to the
111-
// final list manually. See additional information in the `createAnchorNode`
112-
// function in the `view_container_ref.ts`.
113-
//
114-
// In the first case, the same reference would be stored in the `NATIVE`
115-
// and `HOST` slots in an LContainer. Otherwise, this is the second case and
116-
// we should add an element to the final list.
117-
if (lContainer[NATIVE] !== lContainer[HOST]) {
118-
result.push(lContainer[NATIVE]);
119-
}
109+
// The container's anchor comment node (NATIVE) is always physically positioned
110+
// after any views rendered inside the container, so we always push it here at the end.
111+
result.push(lContainer[NATIVE]);
120112
}

packages/core/test/acceptance/template_ref_spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ describe('TemplateRef', () => {
115115
</ng-template>`);
116116

117117
expect(rootNodes.length).toBe(3);
118-
expect(rootNodes[0].nodeType).toBe(Node.COMMENT_NODE);
119-
expect(rootNodes[1].nodeType).toBe(Node.TEXT_NODE);
118+
expect(rootNodes[0].nodeType).toBe(Node.TEXT_NODE);
119+
expect(rootNodes[1].nodeType).toBe(Node.COMMENT_NODE);
120120
expect(rootNodes[2].nodeType).toBe(Node.TEXT_NODE);
121121
});
122122

@@ -153,8 +153,8 @@ describe('TemplateRef', () => {
153153
`);
154154

155155
expect(rootNodes.length).toBe(3);
156-
expect(rootNodes[0].nodeType).toBe(Node.COMMENT_NODE);
157-
expect(rootNodes[1].nodeType).toBe(Node.TEXT_NODE);
156+
expect(rootNodes[0].nodeType).toBe(Node.TEXT_NODE);
157+
expect(rootNodes[1].nodeType).toBe(Node.COMMENT_NODE);
158158
expect(rootNodes[2].nodeType).toBe(Node.TEXT_NODE);
159159
});
160160

0 commit comments

Comments
 (0)