From a5167bd53cc86a1b2aad4defc820413492a44824 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Wed, 30 Oct 2019 13:39:55 +0100 Subject: [PATCH] fix(ivy): descend into view containers on ng-container when collecting rootNodes (#33493) PR Close #33493 --- packages/core/src/render3/view_ref.ts | 40 ++++++++++--------- .../core/test/acceptance/template_ref_spec.ts | 32 +++++++++++++++ 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 0d48a5578f3ab..a97d2dcf3f282 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -13,14 +13,13 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEn import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions/shared'; import {CONTAINER_HEADER_OFFSET} from './interfaces/container'; -import {LContainer} from './interfaces/container'; import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {isLContainer} from './interfaces/type_checks'; import {CONTEXT, FLAGS, HOST, LView, LViewFlags, TVIEW, T_HOST} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert'; import {destroyLView, renderDetachView} from './node_manipulation'; import {findComponentView, getLViewParent} from './util/view_traversal_utils'; -import {getNativeByTNode, getNativeByTNodeOrNull} from './util/view_utils'; +import {getNativeByTNode, unwrapRNode} from './util/view_utils'; @@ -303,29 +302,32 @@ export class RootViewRef extends ViewRef { get context(): T { return null !; } } -function collectNativeNodesFromAContainer(lContainer: LContainer, result: any[]) { - for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { - const lViewInAContainer = lContainer[i]; - const lViewFirstChildTNode = lViewInAContainer[TVIEW].firstChild; - if (lViewFirstChildTNode !== null) { - collectNativeNodes(lViewInAContainer, lViewFirstChildTNode, result); - } - } -} - function collectNativeNodes(lView: LView, tNode: TNode | null, result: any[]): any[] { while (tNode !== null) { ngDevMode && assertNodeOfPossibleTypes( tNode, TNodeType.Element, TNodeType.Container, TNodeType.Projection, TNodeType.ElementContainer); - const nativeNode = getNativeByTNodeOrNull(tNode, lView); - nativeNode && result.push(nativeNode); - if (tNode.type === TNodeType.Element && isLContainer(lView[tNode.index])) { - collectNativeNodesFromAContainer(lView[tNode.index], result); - } else if (tNode.type === TNodeType.ElementContainer) { + + const lNode = lView[tNode.index]; + if (lNode !== null) { + result.push(unwrapRNode(lNode)); + } + + // A given lNode can represent either a native node or a LContainer (when it is a host of a + // ViewContainerRef). When we find a LContainer we need to descend into it to collect root nodes + // from the views in this container. + if (isLContainer(lNode)) { + for (let i = CONTAINER_HEADER_OFFSET; i < lNode.length; i++) { + const lViewInAContainer = lNode[i]; + const lViewFirstChildTNode = lViewInAContainer[TVIEW].firstChild; + if (lViewFirstChildTNode !== null) { + collectNativeNodes(lViewInAContainer, lViewFirstChildTNode, result); + } + } + } + + if (tNode.type === TNodeType.ElementContainer) { collectNativeNodes(lView, tNode.child, result); - } else if (tNode.type === TNodeType.Container) { - collectNativeNodesFromAContainer(lView[tNode.index], result); } else if (tNode.type === TNodeType.Projection) { const componentView = findComponentView(lView); const componentHost = componentView[T_HOST] as TElementNode; diff --git a/packages/core/test/acceptance/template_ref_spec.ts b/packages/core/test/acceptance/template_ref_spec.ts index 7256c8f5de987..8a470d1b3e024 100644 --- a/packages/core/test/acceptance/template_ref_spec.ts +++ b/packages/core/test/acceptance/template_ref_spec.ts @@ -171,6 +171,38 @@ describe('TemplateRef', () => { expect(embeddedView.rootNodes[2].nodeType).toBe(Node.TEXT_NODE); }); + it('should descend into view containers on ng-container', () => { + /** + * NOTE: In VE, if `SUFFIX` text node below is _not_ present, VE will add an + * additional `` comment, thus being slightly different than Ivy. + * (resulting in 1 root node in Ivy and 2 in VE). + */ + @Component({ + template: ` + text + SUFFIX + ` + }) + class App { + @ViewChild('templateRef', {static: true}) + templateRef !: TemplateRef; + } + + TestBed.configureTestingModule({ + declarations: [App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const embeddedView = fixture.componentInstance.templateRef.createEmbeddedView({}); + embeddedView.detectChanges(); + + expect(embeddedView.rootNodes.length).toBe(3); + expect(embeddedView.rootNodes[0].nodeType).toBe(Node.COMMENT_NODE); + expect(embeddedView.rootNodes[1].nodeType).toBe(Node.TEXT_NODE); + expect(embeddedView.rootNodes[2].nodeType).toBe(Node.TEXT_NODE); + }); + it('should descend into element containers when retrieving root nodes', () => { @Component({ template: `