diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 0fa98dc9ef..9497e6b4e9 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -669,12 +669,22 @@ export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: L if (nextViewIndex < lContainer.length) { const lView = lContainer[nextViewIndex] as LView; ngDevMode && assertDefined(lView[T_HOST], 'Missing Host TNode'); - const tViewNodeChild = (lView[T_HOST] as TViewNode).child; - return tViewNodeChild !== null ? getNativeByTNodeOrNull(tViewNodeChild, lView) : - lContainer[NATIVE]; - } else { - return lContainer[NATIVE]; + let tViewNodeChild = (lView[T_HOST] as TViewNode).child; + if (tViewNodeChild !== null) { + if (tViewNodeChild.type === TNodeType.ElementContainer || + tViewNodeChild.type === TNodeType.IcuContainer) { + let currentChild = tViewNodeChild.child; + while (currentChild && (currentChild.type === TNodeType.ElementContainer || + currentChild.type === TNodeType.IcuContainer)) { + currentChild = currentChild.child; + } + tViewNodeChild = currentChild || tViewNodeChild; + } + return getNativeByTNodeOrNull(tViewNodeChild, lView); + } } + + return lContainer[NATIVE]; } /** diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index a210dc3225..8fba782111 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -1276,6 +1276,178 @@ describe('ViewContainerRef', () => { ''); }); + it('should insert elements in the proper order when template root is an ng-container', () => { + @Component({ + template: ` + |{{ item }}| + ` + }) + class App { + items = ['one', 'two', 'three']; + } + + TestBed.configureTestingModule({imports: [CommonModule], declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|one||two||three|'); + + fixture.componentInstance.items.unshift('zero'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three|'); + + fixture.componentInstance.items.push('four'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three||four|'); + + fixture.componentInstance.items.splice(3, 0, 'two point five'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent) + .toBe('|zero||one||two||two point five||three||four|'); + }); + + it('should insert elements in the proper order when template root is an ng-container and is wrapped by an ng-container', + () => { + @Component({ + template: ` + + |{{ item }}| + + ` + }) + class App { + items = ['one', 'two', 'three']; + } + + TestBed.configureTestingModule({imports: [CommonModule], declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|one||two||three|'); + + fixture.componentInstance.items.unshift('zero'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three|'); + + fixture.componentInstance.items.push('four'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three||four|'); + + fixture.componentInstance.items.splice(3, 0, 'two point five'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent) + .toBe('|zero||one||two||two point five||three||four|'); + }); + + it('should insert elements in the proper order when template root is an ng-container and first node is a ng-container', + () => { + @Component({ + template: ` + |{{ item }}| + ` + }) + class App { + items = ['one', 'two', 'three']; + } + + TestBed.configureTestingModule({imports: [CommonModule], declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|one||two||three|'); + + fixture.componentInstance.items.unshift('zero'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three|'); + + fixture.componentInstance.items.push('four'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three||four|'); + + fixture.componentInstance.items.splice(3, 0, 'two point five'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent) + .toBe('|zero||one||two||two point five||three||four|'); + }); + + it('should insert elements in the proper order when template root is an ng-container, wrapped in an ng-container with the root node as an ng-container', + () => { + @Component({ + template: ` + + |{{ item }}| + + ` + }) + class App { + items = ['one', 'two', 'three']; + } + + TestBed.configureTestingModule({imports: [CommonModule], declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|one||two||three|'); + + fixture.componentInstance.items.unshift('zero'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three|'); + + fixture.componentInstance.items.push('four'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three||four|'); + + fixture.componentInstance.items.splice(3, 0, 'two point five'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent) + .toBe('|zero||one||two||two point five||three||four|'); + }); + + it('should insert elements in the proper order when the first child node is an ICU expression', + () => { + @Component({ + template: ` + {count, select, other {|{{ item }}|}} + ` + }) + class App { + items = ['one', 'two', 'three']; + } + + TestBed.configureTestingModule({imports: [CommonModule], declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|one||two||three|'); + + fixture.componentInstance.items.unshift('zero'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three|'); + + fixture.componentInstance.items.push('four'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('|zero||one||two||three||four|'); + + fixture.componentInstance.items.splice(3, 0, 'two point five'); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent) + .toBe('|zero||one||two||two point five||three||four|'); + }); }); describe('lifecycle hooks', () => {