diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 1dbd3211064a8..fa2794621290e 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -10,10 +10,13 @@ import {ApplicationRef} from '../application_ref'; import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref'; import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref'; import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEngine_InternalViewRef} from '../linker/view_ref'; +import {assertDefined} from '../util/assert'; import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions/shared'; +import {CONTAINER_HEADER_OFFSET} from './interfaces/container'; import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; -import {CONTEXT, FLAGS, HOST, LView, LViewFlags, T_HOST} from './interfaces/view'; +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'; @@ -38,7 +41,7 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int get rootNodes(): any[] { if (this._lView[HOST] == null) { const tView = this._lView[T_HOST] as TViewNode; - return collectNativeNodes(this._lView, tView, []); + return collectNativeNodes(this._lView, tView.child, []); } return []; } @@ -299,27 +302,39 @@ export class RootViewRef extends ViewRef { get context(): T { return null !; } } -function collectNativeNodes(lView: LView, parentTNode: TNode, result: any[]): any[] { - let tNodeChild = parentTNode.child; - - while (tNodeChild) { - const nativeNode = getNativeByTNodeOrNull(tNodeChild, lView); +function collectNativeNodes(lView: LView, tNode: TNode | null, result: any[]): any[] { + while (tNode) { + ngDevMode && assertNodeOfPossibleTypes( + tNode, TNodeType.Element, TNodeType.Container, TNodeType.Projection, + TNodeType.ElementContainer); + const nativeNode = getNativeByTNodeOrNull(tNode, lView); nativeNode && result.push(nativeNode); - if (tNodeChild.type === TNodeType.ElementContainer) { - collectNativeNodes(lView, tNodeChild, result); - } else if (tNodeChild.type === TNodeType.Projection) { + + if (tNode.type === TNodeType.ElementContainer) { + collectNativeNodes(lView, tNode.child, result); + } else if (tNode.type === TNodeType.Container) { + const lContainer = lView[tNode.index]; + const containerTView = tNode.tViews !as TView; + ngDevMode && assertDefined(containerTView, 'TView expected'); + const containerFirstChildTNode = containerTView.firstChild !; + if (containerFirstChildTNode !== null) { + for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { + collectNativeNodes(lContainer[i], containerFirstChildTNode, result); + } + } + } else if (tNode.type === TNodeType.Projection) { const componentView = findComponentView(lView); const componentHost = componentView[T_HOST] as TElementNode; const parentView = getLViewParent(componentView); let currentProjectedNode: TNode|null = - (componentHost.projection as(TNode | null)[])[tNodeChild.projection as number]; + (componentHost.projection as(TNode | null)[])[tNode.projection as number]; while (currentProjectedNode && parentView) { result.push(getNativeByTNode(currentProjectedNode, parentView)); currentProjectedNode = currentProjectedNode.next; } } - tNodeChild = tNodeChild.next; + tNode = tNode.next; } return result; diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index e393c932f4ce3..7798018404e00 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -8,7 +8,7 @@ import {CommonModule, DOCUMENT} from '@angular/common'; import {computeMsgId} from '@angular/compiler'; -import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core'; +import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, NO_ERRORS_SCHEMA, NgModule, OnInit, Pipe, PipeTransform, QueryList, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core'; import {Input} from '@angular/core/src/metadata'; import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode'; import {TestBed, TestComponentRenderer} from '@angular/core/testing'; @@ -872,6 +872,65 @@ describe('ViewContainerRef', () => { '**A****C****C****B**'); }); + it('should recurs into child views when computing rootNodes elements', () => { + class MyDirEmbedded { + // This is a marker for easier debugging. + } + + @Directive({selector: '[dir]'}) + class MyDir { + viewRef: EmbeddedViewRef<{}>|null = null; + + @Input() dir: any; + + constructor(public template: TemplateRef<{}>, public viewContainerRef: ViewContainerRef) { + this.viewRef = + this.viewContainerRef.createEmbeddedView(this.template, new MyDirEmbedded()); + this.viewRef.detectChanges(); + } + } + + @Component({ + selector: 'edit-form', + template: ` + +
Text
+ container-content +
assert TNodeType.IcuContainer is never a root node
+ assert TNodeType.IcuContainer is never a root node +
+ `, + }) + class MyComp { + @ViewChild(MyDir, {static: true}) dir !: MyDir; + } + + TestBed.configureTestingModule({ + declarations: [MyComp, MyDir], + imports: [CommonModule], + }); + + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const dirRef = fixture.componentInstance.dir; + + // Expecting: + // - One comment node for ngIf anchor. + // - One
Text
node for the ngIf content. + // - VE ONLY: Extra comment node It is unclear why the VE adds the last one. + const rootNodes = dirRef.viewRef !.rootNodes; + expect(rootNodes.length).toBe(7); + expect(rootNodes[0].textContent).toBe('bindings={\n "ng-reflect-ng-if": "true"\n}'); + expect(rootNodes[1].outerHTML).toBe('
Text
'); + expect(rootNodes[2].textContent).toBe(ivyEnabled ? 'ng-container' : ''); + expect(rootNodes[3].textContent).toBe('container-content'); + expect(rootNodes[4].outerHTML) + .toBe('
assert TNodeType.IcuContainer is never a root node
'); + expect(rootNodes[5].textContent).toBe(ivyEnabled ? 'ng-container' : ''); + expect(rootNodes[6].textContent).toBe('assert TNodeType.IcuContainer is never a root node'); + }); + }); describe('createComponent', () => {