fix(ivy): descend into view containers on ng-container when collecting rootNodes (#33493)
PR Close #33493
This commit is contained in:
parent
87743f1aa1
commit
a5167bd53c
|
@ -13,14 +13,13 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEn
|
||||||
|
|
||||||
import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions/shared';
|
import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootView, detectChangesInternal, markViewDirty, storeCleanupFn} from './instructions/shared';
|
||||||
import {CONTAINER_HEADER_OFFSET} from './interfaces/container';
|
import {CONTAINER_HEADER_OFFSET} from './interfaces/container';
|
||||||
import {LContainer} from './interfaces/container';
|
|
||||||
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||||
import {isLContainer} from './interfaces/type_checks';
|
import {isLContainer} from './interfaces/type_checks';
|
||||||
import {CONTEXT, FLAGS, HOST, LView, LViewFlags, TVIEW, T_HOST} from './interfaces/view';
|
import {CONTEXT, FLAGS, HOST, LView, LViewFlags, TVIEW, T_HOST} from './interfaces/view';
|
||||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||||
import {destroyLView, renderDetachView} from './node_manipulation';
|
import {destroyLView, renderDetachView} from './node_manipulation';
|
||||||
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
|
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<T> extends ViewRef<T> {
|
||||||
get context(): T { return null !; }
|
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[] {
|
function collectNativeNodes(lView: LView, tNode: TNode | null, result: any[]): any[] {
|
||||||
while (tNode !== null) {
|
while (tNode !== null) {
|
||||||
ngDevMode && assertNodeOfPossibleTypes(
|
ngDevMode && assertNodeOfPossibleTypes(
|
||||||
tNode, TNodeType.Element, TNodeType.Container, TNodeType.Projection,
|
tNode, TNodeType.Element, TNodeType.Container, TNodeType.Projection,
|
||||||
TNodeType.ElementContainer);
|
TNodeType.ElementContainer);
|
||||||
const nativeNode = getNativeByTNodeOrNull(tNode, lView);
|
|
||||||
nativeNode && result.push(nativeNode);
|
const lNode = lView[tNode.index];
|
||||||
if (tNode.type === TNodeType.Element && isLContainer(lView[tNode.index])) {
|
if (lNode !== null) {
|
||||||
collectNativeNodesFromAContainer(lView[tNode.index], result);
|
result.push(unwrapRNode(lNode));
|
||||||
} else if (tNode.type === TNodeType.ElementContainer) {
|
}
|
||||||
|
|
||||||
|
// 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);
|
collectNativeNodes(lView, tNode.child, result);
|
||||||
} else if (tNode.type === TNodeType.Container) {
|
|
||||||
collectNativeNodesFromAContainer(lView[tNode.index], result);
|
|
||||||
} else if (tNode.type === TNodeType.Projection) {
|
} else if (tNode.type === TNodeType.Projection) {
|
||||||
const componentView = findComponentView(lView);
|
const componentView = findComponentView(lView);
|
||||||
const componentHost = componentView[T_HOST] as TElementNode;
|
const componentHost = componentView[T_HOST] as TElementNode;
|
||||||
|
|
|
@ -171,6 +171,38 @@ describe('TemplateRef', () => {
|
||||||
expect(embeddedView.rootNodes[2].nodeType).toBe(Node.TEXT_NODE);
|
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: `
|
||||||
|
<ng-template #dynamicTpl>text</ng-template>
|
||||||
|
<ng-template #templateRef><ng-container [ngTemplateOutlet]="dynamicTpl"></ng-container>SUFFIX</ng-template>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
@ViewChild('templateRef', {static: true})
|
||||||
|
templateRef !: TemplateRef<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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', () => {
|
it('should descend into element containers when retrieving root nodes', () => {
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
|
|
Loading…
Reference in New Issue