From 7ca611cd1266ede26a26b17cb31748988ff8d98c Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 27 Jun 2019 12:11:05 +0200 Subject: [PATCH] fix(ivy): properly handle re-projection with an empty set of nodes to re-project (#31306) PR Close #31306 --- .../core/src/render3/node_manipulation.ts | 41 +++++++++++-------- .../acceptance/view_container_ref_spec.ts | 27 ++++++++++++ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 80925708af..57dfe12869 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -780,6 +780,7 @@ function executeActionOnView( * @param renderer Renderer to use * @param action action to perform (insert, detach, destroy) * @param lView The LView which needs to be inserted, detached, destroyed. + * @param tProjectionNode projection TNode to process * @param renderParent parent DOM element for insertion/removal. * @param beforeNode Before which node the insertions should happen. */ @@ -789,20 +790,25 @@ function executeActionOnProjection( beforeNode: RNode | null | undefined) { const componentLView = findComponentView(lView); const componentNode = componentLView[T_HOST] as TElementNode; - const nodeToProject = componentNode.projection ![tProjectionNode.projection] !; - if (Array.isArray(nodeToProject)) { - for (let i = 0; i < nodeToProject.length; i++) { - const rNode = nodeToProject[i]; - ngDevMode && assertDomNode(rNode); - executeActionOnElementOrContainer(action, renderer, renderParent, rNode, beforeNode); - } - } else { - let projectionTNode: TNode|null = nodeToProject; - const projectedComponentLView = componentLView[PARENT] as LView; - while (projectionTNode !== null) { - executeActionOnNode( - renderer, action, projectedComponentLView, projectionTNode, renderParent, beforeNode); - projectionTNode = projectionTNode.projectionNext; + ngDevMode && assertDefined( + componentNode.projection, + 'Element nodes for which projection is processed must have projection defined.'); + const nodeToProject = componentNode.projection ![tProjectionNode.projection]; + if (nodeToProject !== undefined) { + if (Array.isArray(nodeToProject)) { + for (let i = 0; i < nodeToProject.length; i++) { + const rNode = nodeToProject[i]; + ngDevMode && assertDomNode(rNode); + executeActionOnElementOrContainer(action, renderer, renderParent, rNode, beforeNode); + } + } else { + let projectionTNode: TNode|null = nodeToProject; + const projectedComponentLView = componentLView[PARENT] as LView; + while (projectionTNode !== null) { + executeActionOnNode( + renderer, action, projectedComponentLView, projectionTNode, renderParent, beforeNode); + projectionTNode = projectionTNode.projectionNext; + } } } } @@ -870,13 +876,12 @@ function executeActionOnElementContainerOrIcuContainer( function executeActionOnNode( renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView, tNode: TNode, renderParent: RElement | null, beforeNode: RNode | null | undefined): void { - const elementContainerRootTNodeType = tNode.type; - if (elementContainerRootTNodeType === TNodeType.ElementContainer || - elementContainerRootTNodeType === TNodeType.IcuContainer) { + const nodeType = tNode.type; + if (nodeType === TNodeType.ElementContainer || nodeType === TNodeType.IcuContainer) { executeActionOnElementContainerOrIcuContainer( renderer, action, lView, tNode as TElementContainerNode | TIcuContainerNode, renderParent, beforeNode); - } else if (elementContainerRootTNodeType === TNodeType.Projection) { + } else if (nodeType === TNodeType.Projection) { executeActionOnProjection( renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode); } else { diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index 186be8a9e4..0e672212f1 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -1551,6 +1551,33 @@ describe('ViewContainerRef', () => { 'Before (inside)- Before projected
blah
bar After projected -After (inside)
'); }); + it('should handle empty re-projection into the root of a view', () => { + @Component({ + selector: 'root-comp', + template: ``, + }) + class RootComp { + @Input() show: boolean = true; + } + + @Component({ + selector: 'my-app', + template: `
` + }) + class MyApp { + show = true; + } + + TestBed.configureTestingModule({declarations: [MyApp, RootComp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelectorAll('div').length).toBe(1); + + fixture.componentInstance.show = false; + fixture.detectChanges(); + expect(fixture.nativeElement.querySelectorAll('div').length).toBe(0); + }); + describe('with select', () => { @Component({