Skip to content

Commit 680d385

Browse files
ocombemhevery
authored andcommitted
fix(ivy): correctly project bare ICU expressions (angular#30696)
Projecting bare ICU expressions failed because we would assume that component's content nodes would be projected later and doing so at that point would be wasteful. But ICU nodes are handled independently and should be inserted immediately because they will be ignored by projections. FW-1348 #resolve PR Close angular#30696
1 parent 21328f2 commit 680d385

File tree

3 files changed

+43
-8
lines changed

3 files changed

+43
-8
lines changed

integration/_payload-limits.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"master": {
1313
"uncompressed": {
1414
"runtime": 1440,
15-
"main": 14487,
15+
"main": 14664,
1616
"polyfills": 43567
1717
}
1818
}

packages/core/src/render3/node_manipulation.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {ComponentDef} from './interfaces/definition';
1515
import {NodeInjectorFactory} from './interfaces/injector';
1616
import {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
1717
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
18-
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
18+
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
1919
import {CHILD_HEAD, CLEANUP, FLAGS, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
2020
import {assertNodeType} from './node_assert';
2121
import {renderStringify} from './util/misc_utils';
@@ -558,11 +558,12 @@ function getRenderParent(tNode: TNode, currentView: LView): RElement|null {
558558

559559
// Skip over element and ICU containers as those are represented by a comment node and
560560
// can't be used as a render parent.
561-
const parent = getHighestElementOrICUContainer(tNode).parent;
561+
const parent = getHighestElementOrICUContainer(tNode);
562+
const renderParent = parent.parent;
562563

563564
// If the parent is null, then we are inserting across views: either into an embedded view or a
564565
// component view.
565-
if (parent == null) {
566+
if (renderParent == null) {
566567
const hostTNode = currentView[T_HOST] !;
567568
if (hostTNode.type === TNodeType.View) {
568569
// We are inserting a root element of an embedded view We might delay insertion of children
@@ -579,10 +580,17 @@ function getRenderParent(tNode: TNode, currentView: LView): RElement|null {
579580
return getHostNative(currentView);
580581
}
581582
} else {
582-
ngDevMode && assertNodeType(parent, TNodeType.Element);
583-
if (parent.flags & TNodeFlags.isComponent) {
583+
const isIcuCase = parent && parent.type === TNodeType.IcuContainer;
584+
// If the parent of this node is an ICU container, then it is represented by comment node and we
585+
// need to use it as an anchor. If it is projected then its direct parent node is the renderer.
586+
if (isIcuCase && parent.flags & TNodeFlags.isProjected) {
587+
return getNativeByTNode(parent, currentView).parentNode as RElement;
588+
}
589+
590+
ngDevMode && assertNodeType(renderParent, TNodeType.Element);
591+
if (renderParent.flags & TNodeFlags.isComponent && !isIcuCase) {
584592
const tData = currentView[TVIEW].data;
585-
const tNode = tData[parent.index] as TNode;
593+
const tNode = tData[renderParent.index] as TNode;
586594
const encapsulation = (tData[tNode.directiveStart] as ComponentDef<any>).encapsulation;
587595

588596
// We've got a parent which is an element in the current view. We just need to verify if the
@@ -597,7 +605,7 @@ function getRenderParent(tNode: TNode, currentView: LView): RElement|null {
597605
}
598606
}
599607

600-
return getNativeByTNode(parent, currentView) as RElement;
608+
return getNativeByTNode(renderParent, currentView) as RElement;
601609
}
602610
}
603611

packages/core/test/acceptance/i18n_spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,33 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
591591
fixture.detectChanges();
592592
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->');
593593
});
594+
595+
it('projection', () => {
596+
@Component({selector: 'child', template: '<div><ng-content></ng-content></div>'})
597+
class Child {
598+
}
599+
600+
@Component({
601+
selector: 'parent',
602+
template: `
603+
<child i18n>{
604+
value // i18n(ph = "blah"),
605+
plural,
606+
=1 {one}
607+
other {at least {{value}} .}
608+
}</child>`
609+
})
610+
class Parent {
611+
value = 3;
612+
}
613+
TestBed.configureTestingModule({declarations: [Parent, Child]});
614+
ɵi18nConfigureLocalize({translations: {}});
615+
616+
const fixture = TestBed.createComponent(Parent);
617+
fixture.detectChanges();
618+
619+
expect(fixture.nativeElement.innerHTML).toContain('at least');
620+
});
594621
});
595622

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

0 commit comments

Comments
 (0)