From 680d38513b4da21b30e2c92103c66942db5e46dd Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Mon, 3 Jun 2019 15:05:34 +0200 Subject: [PATCH] fix(ivy): correctly project bare ICU expressions (#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 #30696 --- integration/_payload-limits.json | 2 +- .../core/src/render3/node_manipulation.ts | 22 ++++++++++----- packages/core/test/acceptance/i18n_spec.ts | 27 +++++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index cc45acecbb..35c68b565d 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime": 1440, - "main": 14487, + "main": 14664, "polyfills": 43567 } } diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 8a586c5fd7..8fc447f32b 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -15,7 +15,7 @@ import {ComponentDef} from './interfaces/definition'; import {NodeInjectorFactory} from './interfaces/injector'; import {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; -import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; +import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {CHILD_HEAD, CLEANUP, FLAGS, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeType} from './node_assert'; import {renderStringify} from './util/misc_utils'; @@ -558,11 +558,12 @@ function getRenderParent(tNode: TNode, currentView: LView): RElement|null { // Skip over element and ICU containers as those are represented by a comment node and // can't be used as a render parent. - const parent = getHighestElementOrICUContainer(tNode).parent; + const parent = getHighestElementOrICUContainer(tNode); + const renderParent = parent.parent; // If the parent is null, then we are inserting across views: either into an embedded view or a // component view. - if (parent == null) { + if (renderParent == null) { const hostTNode = currentView[T_HOST] !; if (hostTNode.type === TNodeType.View) { // 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 { return getHostNative(currentView); } } else { - ngDevMode && assertNodeType(parent, TNodeType.Element); - if (parent.flags & TNodeFlags.isComponent) { + const isIcuCase = parent && parent.type === TNodeType.IcuContainer; + // If the parent of this node is an ICU container, then it is represented by comment node and we + // need to use it as an anchor. If it is projected then its direct parent node is the renderer. + if (isIcuCase && parent.flags & TNodeFlags.isProjected) { + return getNativeByTNode(parent, currentView).parentNode as RElement; + } + + ngDevMode && assertNodeType(renderParent, TNodeType.Element); + if (renderParent.flags & TNodeFlags.isComponent && !isIcuCase) { const tData = currentView[TVIEW].data; - const tNode = tData[parent.index] as TNode; + const tNode = tData[renderParent.index] as TNode; const encapsulation = (tData[tNode.directiveStart] as ComponentDef).encapsulation; // 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 { } } - return getNativeByTNode(parent, currentView) as RElement; + return getNativeByTNode(renderParent, currentView) as RElement; } } diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index ad3b47fc97..a71d17304c 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -591,6 +591,33 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.detectChanges(); expect(fixture.nativeElement.innerHTML).toEqual('no email'); }); + + it('projection', () => { + @Component({selector: 'child', template: '
'}) + class Child { + } + + @Component({ + selector: 'parent', + template: ` + { + value // i18n(ph = "blah"), + plural, + =1 {one} + other {at least {{value}} .} + }` + }) + class Parent { + value = 3; + } + TestBed.configureTestingModule({declarations: [Parent, Child]}); + ɵi18nConfigureLocalize({translations: {}}); + + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(fixture.nativeElement.innerHTML).toContain('at least'); + }); }); describe('should support attributes', () => {