diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 50dd8cd1d9..5d78c9c8d2 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -7,7 +7,6 @@ */ import {ViewEncapsulation} from '../metadata/view'; - import {assertLContainer, assertLView} from './assert'; import {attachPatchData} from './context_discovery'; import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; @@ -801,6 +800,23 @@ export function appendProjectedNodes( } } +/** + * Loops over all children of a TNode container and appends them to the DOM + * + * @param ngContainerChildTNode The first child of the TNode container + * @param tProjectionNode The projection (ng-content) TNode + * @param currentView Current LView + * @param projectionView Projection view (view above current) + */ +function appendProjectedChildren( + ngContainerChildTNode: TNode | null, tProjectionNode: TNode, currentView: LView, + projectionView: LView) { + while (ngContainerChildTNode) { + appendProjectedNode(ngContainerChildTNode, tProjectionNode, currentView, projectionView); + ngContainerChildTNode = ngContainerChildTNode.next; + } +} + /** * Appends a projected node to the DOM, or in the case of a projected container, * appends the nodes from all of the container's active views to the DOM. @@ -831,13 +847,15 @@ function appendProjectedNode( for (let i = CONTAINER_HEADER_OFFSET; i < nodeOrContainer.length; i++) { addRemoveViewFromContainer(nodeOrContainer[i], true, nodeOrContainer[NATIVE]); } + } else if (projectedTNode.type === TNodeType.IcuContainer) { + // The node we are adding is an ICU container which is why we also need to project all the + // children nodes that might have been created previously and are linked to this anchor + let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode; + appendProjectedChildren( + ngContainerChildTNode, ngContainerChildTNode, projectionView, projectionView); } else { if (projectedTNode.type === TNodeType.ElementContainer) { - let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode; - while (ngContainerChildTNode) { - appendProjectedNode(ngContainerChildTNode, tProjectionNode, currentView, projectionView); - ngContainerChildTNode = ngContainerChildTNode.next; - } + appendProjectedChildren(projectedTNode.child, tProjectionNode, currentView, projectionView); } if (isLContainer(nodeOrContainer)) { diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 10eb551258..ef4c5c86dc 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -661,6 +661,117 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { const element = fixture.nativeElement; expect(element).toHaveText('other'); }); + + it('inside a container when creating a view via vcr.createEmbeddedView', () => { + @Directive({ + selector: '[someDir]', + }) + class Dir { + constructor( + private readonly viewContainerRef: ViewContainerRef, + private readonly templateRef: TemplateRef) {} + + ngOnInit() { this.viewContainerRef.createEmbeddedView(this.templateRef); } + } + + @Component({ + selector: 'my-cmp', + template: ` +
+ +
+ `, + }) + class Cmp { + } + + @Component({ + selector: 'my-app', + template: ` + { + count, + plural, + =1 {ONE} + other {OTHER} + } + `, + }) + class App { + count = 1; + } + + TestBed.configureTestingModule({ + declarations: [App, Cmp, Dir], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.innerHTML) + .toBe('
ONE
'); + + fixture.componentRef.instance.count = 2; + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.innerHTML) + .toBe('
OTHER
'); + }); + + it('with nested ICU expression and inside a container when creating a view via vcr.createEmbeddedView', + () => { + @Directive({ + selector: '[someDir]', + }) + class Dir { + constructor( + private readonly viewContainerRef: ViewContainerRef, + private readonly templateRef: TemplateRef) {} + + ngOnInit() { this.viewContainerRef.createEmbeddedView(this.templateRef); } + } + + @Component({ + selector: 'my-cmp', + template: ` +
+ +
+ `, + }) + class Cmp { + } + + @Component({ + selector: 'my-app', + template: ` + { + count, + plural, + =1 {ONE} + other {{{count}} {name, select, + cat {cats} + dog {dogs} + other {animals} + }!} + } + `, + }) + class App { + count = 1; + } + + TestBed.configureTestingModule({ + declarations: [App, Cmp, Dir], + }); + const fixture = TestBed.createComponent(App); + fixture.componentRef.instance.count = 2; + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.innerHTML) + .toBe( + '
2 animals!
'); + + fixture.componentRef.instance.count = 1; + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.innerHTML) + .toBe('
ONE
'); + }); }); describe('should support attributes', () => {