Skip to content

Commit 65544ac

Browse files
ocombeAndrewKushnir
authored andcommitted
fix(ivy): reprojected ICU expression nodes when creating embedded views (angular#30979)
When using `createEmbeddedView` after the creation of an ICU expression, the nodes for the current selected case were not reprojected (only the anchor comment node was moved to the new location). Now we reproject correctly all the child nodes of an ICU expression when an anchor comment node is projected. FW-1372 #resolve PR Close angular#30979
1 parent 57c4788 commit 65544ac

File tree

2 files changed

+135
-6
lines changed

2 files changed

+135
-6
lines changed

packages/core/src/render3/node_manipulation.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import {ViewEncapsulation} from '../metadata/view';
10-
1110
import {assertLContainer, assertLView} from './assert';
1211
import {attachPatchData} from './context_discovery';
1312
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
@@ -801,6 +800,23 @@ export function appendProjectedNodes(
801800
}
802801
}
803802

803+
/**
804+
* Loops over all children of a TNode container and appends them to the DOM
805+
*
806+
* @param ngContainerChildTNode The first child of the TNode container
807+
* @param tProjectionNode The projection (ng-content) TNode
808+
* @param currentView Current LView
809+
* @param projectionView Projection view (view above current)
810+
*/
811+
function appendProjectedChildren(
812+
ngContainerChildTNode: TNode | null, tProjectionNode: TNode, currentView: LView,
813+
projectionView: LView) {
814+
while (ngContainerChildTNode) {
815+
appendProjectedNode(ngContainerChildTNode, tProjectionNode, currentView, projectionView);
816+
ngContainerChildTNode = ngContainerChildTNode.next;
817+
}
818+
}
819+
804820
/**
805821
* Appends a projected node to the DOM, or in the case of a projected container,
806822
* appends the nodes from all of the container's active views to the DOM.
@@ -831,13 +847,15 @@ function appendProjectedNode(
831847
for (let i = CONTAINER_HEADER_OFFSET; i < nodeOrContainer.length; i++) {
832848
addRemoveViewFromContainer(nodeOrContainer[i], true, nodeOrContainer[NATIVE]);
833849
}
850+
} else if (projectedTNode.type === TNodeType.IcuContainer) {
851+
// The node we are adding is an ICU container which is why we also need to project all the
852+
// children nodes that might have been created previously and are linked to this anchor
853+
let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode;
854+
appendProjectedChildren(
855+
ngContainerChildTNode, ngContainerChildTNode, projectionView, projectionView);
834856
} else {
835857
if (projectedTNode.type === TNodeType.ElementContainer) {
836-
let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode;
837-
while (ngContainerChildTNode) {
838-
appendProjectedNode(ngContainerChildTNode, tProjectionNode, currentView, projectionView);
839-
ngContainerChildTNode = ngContainerChildTNode.next;
840-
}
858+
appendProjectedChildren(projectedTNode.child, tProjectionNode, currentView, projectionView);
841859
}
842860

843861
if (isLContainer(nodeOrContainer)) {

packages/core/test/acceptance/i18n_spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,117 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
661661
const element = fixture.nativeElement;
662662
expect(element).toHaveText('other');
663663
});
664+
665+
it('inside a container when creating a view via vcr.createEmbeddedView', () => {
666+
@Directive({
667+
selector: '[someDir]',
668+
})
669+
class Dir {
670+
constructor(
671+
private readonly viewContainerRef: ViewContainerRef,
672+
private readonly templateRef: TemplateRef<any>) {}
673+
674+
ngOnInit() { this.viewContainerRef.createEmbeddedView(this.templateRef); }
675+
}
676+
677+
@Component({
678+
selector: 'my-cmp',
679+
template: `
680+
<div *someDir>
681+
<ng-content></ng-content>
682+
</div>
683+
`,
684+
})
685+
class Cmp {
686+
}
687+
688+
@Component({
689+
selector: 'my-app',
690+
template: `
691+
<my-cmp i18n="test">{
692+
count,
693+
plural,
694+
=1 {ONE}
695+
other {OTHER}
696+
}</my-cmp>
697+
`,
698+
})
699+
class App {
700+
count = 1;
701+
}
702+
703+
TestBed.configureTestingModule({
704+
declarations: [App, Cmp, Dir],
705+
});
706+
const fixture = TestBed.createComponent(App);
707+
fixture.detectChanges();
708+
expect(fixture.debugElement.nativeElement.innerHTML)
709+
.toBe('<my-cmp><div>ONE<!--ICU 13--></div><!--container--></my-cmp>');
710+
711+
fixture.componentRef.instance.count = 2;
712+
fixture.detectChanges();
713+
expect(fixture.debugElement.nativeElement.innerHTML)
714+
.toBe('<my-cmp><div>OTHER<!--ICU 13--></div><!--container--></my-cmp>');
715+
});
716+
717+
it('with nested ICU expression and inside a container when creating a view via vcr.createEmbeddedView',
718+
() => {
719+
@Directive({
720+
selector: '[someDir]',
721+
})
722+
class Dir {
723+
constructor(
724+
private readonly viewContainerRef: ViewContainerRef,
725+
private readonly templateRef: TemplateRef<any>) {}
726+
727+
ngOnInit() { this.viewContainerRef.createEmbeddedView(this.templateRef); }
728+
}
729+
730+
@Component({
731+
selector: 'my-cmp',
732+
template: `
733+
<div *someDir>
734+
<ng-content></ng-content>
735+
</div>
736+
`,
737+
})
738+
class Cmp {
739+
}
740+
741+
@Component({
742+
selector: 'my-app',
743+
template: `
744+
<my-cmp i18n="test">{
745+
count,
746+
plural,
747+
=1 {ONE}
748+
other {{{count}} {name, select,
749+
cat {cats}
750+
dog {dogs}
751+
other {animals}
752+
}!}
753+
}</my-cmp>
754+
`,
755+
})
756+
class App {
757+
count = 1;
758+
}
759+
760+
TestBed.configureTestingModule({
761+
declarations: [App, Cmp, Dir],
762+
});
763+
const fixture = TestBed.createComponent(App);
764+
fixture.componentRef.instance.count = 2;
765+
fixture.detectChanges();
766+
expect(fixture.debugElement.nativeElement.innerHTML)
767+
.toBe(
768+
'<my-cmp><div>2 animals<!--nested ICU 0-->!<!--ICU 15--></div><!--container--></my-cmp>');
769+
770+
fixture.componentRef.instance.count = 1;
771+
fixture.detectChanges();
772+
expect(fixture.debugElement.nativeElement.innerHTML)
773+
.toBe('<my-cmp><div>ONE<!--ICU 15--></div><!--container--></my-cmp>');
774+
});
664775
});
665776

666777
describe('should support attributes', () => {

0 commit comments

Comments
 (0)