diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index d3ad41abdb..a1228e0df4 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -330,25 +330,28 @@ export function createContainerRef( let commentNode: RComment; // If the host is an element container, the native host element is guaranteed to be a // comment and we can reuse that comment as anchor element for the new LContainer. + // The comment node in question is already part of the DOM structure so we don't need to append + // it again. if (hostTNode.type === TNodeType.ElementContainer) { commentNode = unwrapRNode(slotValue) as RComment; } else { ngDevMode && ngDevMode.rendererCreateComment++; commentNode = hostView[RENDERER].createComment(ngDevMode ? 'container' : ''); - } - // A container can be created on the root (topmost / bootstrapped) component and in this case we - // can't use LTree to insert container's marker node (both parent of a comment node and the - // commend node itself is located outside of elements hold by LTree). In this specific case we - // use low-level DOM manipulation to insert container's marker (comment) node. - if (isRootView(hostView)) { - const renderer = hostView[RENDERER]; - const hostNative = getNativeByTNode(hostTNode, hostView) !; - const parentOfHostNative = nativeParentNode(renderer, hostNative); - nativeInsertBefore( - renderer, parentOfHostNative !, commentNode, nativeNextSibling(renderer, hostNative)); - } else { - appendChild(commentNode, hostTNode, hostView); + // A `ViewContainerRef` can be injected by the root (topmost / bootstrapped) component. In + // this case we can't use TView / TNode data structures to insert container's marker node + // (both a parent of a comment node and the comment node itself are not part of any view). In + // this specific case we use low-level DOM manipulation to insert container's marker (comment) + // node. + if (isRootView(hostView)) { + const renderer = hostView[RENDERER]; + const hostNative = getNativeByTNode(hostTNode, hostView) !; + const parentOfHostNative = nativeParentNode(renderer, hostNative); + nativeInsertBefore( + renderer, parentOfHostNative !, commentNode, nativeNextSibling(renderer, hostNative)); + } else { + appendChild(commentNode, hostTNode, hostView); + } } hostView[hostTNode.index] = lContainer = diff --git a/packages/core/test/acceptance/view_insertion_spec.ts b/packages/core/test/acceptance/view_insertion_spec.ts index eed47660af..06f8c6fcbd 100644 --- a/packages/core/test/acceptance/view_insertion_spec.ts +++ b/packages/core/test/acceptance/view_insertion_spec.ts @@ -541,4 +541,42 @@ describe('view insertion', () => { }); }); + describe('non-regression', () => { + + // https://github.com/angular/angular/issues/33679 + it('should insert views into ViewContainerRef injected by querying ', () => { + + @Component({ + selector: 'app-root', + template: ` +
container start|
+ +
|container end
+ + test +
|click
+ ` + }) + class AppComponent { + @ViewChild('container', {read: ViewContainerRef, static: true}) + vcr !: ViewContainerRef; + + @ViewChild('template', {read: TemplateRef, static: true}) template !: TemplateRef; + + click() { this.vcr.createEmbeddedView(this.template, undefined, 0); } + } + + TestBed.configureTestingModule({ + declarations: [AppComponent], + }); + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toBe('container start||container end|click'); + + fixture.componentInstance.click(); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toBe('container start|test|container end|click'); + }); + + }); });