diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index b5cbb73219..4663868be5 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -502,6 +502,12 @@ function executePipeOnDestroys(viewData: LViewData): void { export function getRenderParent(tNode: TNode, currentView: LViewData): RElement|null { if (canInsertNativeNode(tNode, currentView)) { const hostTNode = currentView[HOST_NODE]; + + const tNodeParent = tNode.parent; + if (tNodeParent != null && tNodeParent.type === TNodeType.ElementContainer) { + tNode = getHighestElementContainer(tNodeParent); + } + return tNode.parent == null && hostTNode !.type === TNodeType.View ? getContainerRenderParent(hostTNode as TViewNode, currentView) : getParentNative(tNode, currentView) as RElement; @@ -626,8 +632,7 @@ export function appendChild( renderer, lContainer[RENDER_PARENT] !, childEl, getBeforeNodeForView(index, views, lContainer[NATIVE])); } else if (parentTNode.type === TNodeType.ElementContainer) { - let elementContainer = getHighestElementContainer(childTNode); - let renderParent: RElement = getRenderParent(elementContainer, currentView) !; + const renderParent: RElement = getRenderParent(childTNode, currentView) !; nativeInsertBefore(renderer, renderParent, childEl, parentEl); } else { isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) : diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 1f17564e53..9426cd40d2 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -943,6 +943,116 @@ describe('render3 integration test', () => { expect(directive !.elRef.nativeElement.nodeType).toBe(Node.COMMENT_NODE); }); + it('should support ViewContainerRef when ng-container is at the root of a view', () => { + + function ContentTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + text(0, 'Content'); + } + } + + class Directive { + contentTpl: TemplateRef<{}>|null = null; + + constructor(private _vcRef: ViewContainerRef) {} + + insertView() { this._vcRef.createEmbeddedView(this.contentTpl as TemplateRef<{}>); } + + clear() { this._vcRef.clear(); } + + static ngDirectiveDef = defineDirective({ + type: Directive, + selectors: [['', 'dir', '']], + factory: () => directive = new Directive(directiveInject(ViewContainerRef as any)), + inputs: {contentTpl: 'contentTpl'}, + }); + } + + let directive: Directive; + + /** + * + * Content + * + */ + const App = createComponent('app', function(rf: RenderFlags) { + if (rf & RenderFlags.Create) { + elementContainerStart(0, [AttributeMarker.SelectOnly, 'dir']); + template(1, ContentTemplate, 1, 0, '', null, ['content', ''], templateRefExtractor); + elementContainerEnd(); + } + if (rf & RenderFlags.Update) { + const content = reference(2) as any; + elementProperty(0, 'contentTpl', bind(content)); + } + }, 3, 1, [Directive]); + + + const fixture = new ComponentFixture(App); + expect(fixture.html).toEqual(''); + + directive !.insertView(); + fixture.update(); + expect(fixture.html).toEqual('Content'); + + directive !.clear(); + fixture.update(); + expect(fixture.html).toEqual(''); + }); + + it('should support ViewContainerRef on inside ', () => { + function ContentTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + text(0, 'Content'); + } + } + + class Directive { + constructor(private _tplRef: TemplateRef<{}>, private _vcRef: ViewContainerRef) {} + + insertView() { this._vcRef.createEmbeddedView(this._tplRef); } + + clear() { this._vcRef.clear(); } + + static ngDirectiveDef = defineDirective({ + type: Directive, + selectors: [['', 'dir', '']], + factory: + () => directive = new Directive( + directiveInject(TemplateRef as any), directiveInject(ViewContainerRef as any)), + }); + } + + let directive: Directive; + + /** + * + * Content + * + */ + const App = createComponent('app', function(rf: RenderFlags) { + if (rf & RenderFlags.Create) { + elementContainerStart(0); + template( + 1, ContentTemplate, 1, 0, '', [AttributeMarker.SelectOnly, 'dir'], [], + templateRefExtractor); + elementContainerEnd(); + } + }, 2, 0, [Directive]); + + + const fixture = new ComponentFixture(App); + expect(fixture.html).toEqual(''); + + directive !.insertView(); + fixture.update(); + expect(fixture.html).toEqual('Content'); + + directive !.clear(); + fixture.update(); + expect(fixture.html).toEqual(''); + }); + it('should not set any attributes', () => { /** *