diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index cacee5f0cc..d6fde0e553 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -19,7 +19,7 @@ import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert'; import {NodeInjector, getParentInjectorLocation} from './di'; import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions/shared'; -import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from './interfaces/container'; +import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from './interfaces/container'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer'; import {CONTEXT, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view'; @@ -29,7 +29,7 @@ import {getParentInjectorTNode} from './node_util'; import {getLView, getPreviousOrParentTNode} from './state'; import {getParentInjectorView, hasParentInjector} from './util/injector_utils'; import {findComponentView} from './util/view_traversal_utils'; -import {getComponentViewByIndex, getNativeByTNode, isComponent, isLContainer, isRootView, viewAttachedToContainer} from './util/view_utils'; +import {getComponentViewByIndex, getNativeByTNode, isComponent, isLContainer, isRootView, unwrapRNode, viewAttachedToContainer} from './util/view_utils'; import {ViewRef} from './view_ref'; @@ -313,8 +313,15 @@ export function createContainerRef( lContainer = slotValue; lContainer[ACTIVE_INDEX] = -1; } else { - const commentNode = hostView[RENDERER].createComment(ngDevMode ? 'container' : ''); - ngDevMode && ngDevMode.rendererCreateComment++; + 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. + 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 diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 647d86c5ec..9679058eb2 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -937,14 +937,14 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.detectChanges(); expect(q.query.length).toEqual(1); expect(toHtml(fixture.nativeElement)) - .toEqual(`Contenu`); + .toEqual(`Contenu`); // Disable ng-if fixture.componentInstance.visible = false; fixture.detectChanges(); expect(q.query.length).toEqual(0); expect(toHtml(fixture.nativeElement)) - .toEqual(`Contenu`); + .toEqual(`Contenu`); }); }); }); diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index 98048b7546..ca428508b4 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -61,6 +61,59 @@ describe('ViewContainerRef', () => { expect(fixture.componentInstance.foo).toBeAnInstanceOf(TemplateRef); }); + it('should use comment node of host ng-container as insertion marker', () => { + @Component({template: 'hello'}) + class HelloComp { + } + + @NgModule({entryComponents: [HelloComp], declarations: [HelloComp]}) + class HelloCompModule { + } + + @Component({ + template: ` + + ` + }) + class TestComp { + @ViewChild(VCRefDirective, {static: true}) vcRefDir !: VCRefDirective; + } + + TestBed.configureTestingModule( + {declarations: [TestComp, VCRefDirective], imports: [HelloCompModule]}); + const fixture = TestBed.createComponent(TestComp); + const {vcref, cfr, elementRef} = fixture.componentInstance.vcRefDir; + fixture.detectChanges(); + + expect(fixture.nativeElement.innerHTML) + .toMatch(//, 'Expected only one comment node to be generated.'); + + const testParent = document.createElement('div'); + testParent.appendChild(elementRef.nativeElement); + + expect(testParent.textContent).toBe(''); + expect(testParent.childNodes.length).toBe(1); + expect(testParent.childNodes[0].nodeType).toBe(Node.COMMENT_NODE); + + // Add a test component to the view container ref to ensure that + // the "ng-container" comment was used as marker for the insertion. + vcref.createComponent(cfr.resolveComponentFactory(HelloComp)); + fixture.detectChanges(); + + expect(testParent.textContent).toBe('hello'); + expect(testParent.childNodes.length).toBe(2); + + // With Ivy, views are inserted before the container comment marker. + if (ivyEnabled) { + expect(testParent.childNodes[0].nodeType).toBe(Node.ELEMENT_NODE); + expect(testParent.childNodes[0].textContent).toBe('hello'); + expect(testParent.childNodes[1].nodeType).toBe(Node.COMMENT_NODE); + } else { + expect(testParent.childNodes[0].nodeType).toBe(Node.COMMENT_NODE); + expect(testParent.childNodes[1].nodeType).toBe(Node.ELEMENT_NODE); + expect(testParent.childNodes[1].textContent).toBe('hello'); + } + }); }); describe('insert', () => { @@ -1668,7 +1721,9 @@ class VCRefDirective { // Injecting the ViewContainerRef to create a dynamic container in which // embedded views will be created - constructor(public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver) {} + constructor( + public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver, + public elementRef: ElementRef) {} createView(s: string, index?: number): EmbeddedViewRef { if (!this.tplRef) {