From 90fb5d9f7a491fa5bf2fe6cc2de56c9bd2ec72b7 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 3 Oct 2019 16:47:40 -0700 Subject: [PATCH] fix(ivy): generate ng-reflect properties for i18n attributes (#32989) Prior to this change, ng-reflect properties were not created in case an attribute was marked as translatable (for ex. `i18n-title`). This commit adds the logic to generate ng-reflect for such cases. PR Close #32989 --- packages/core/src/render3/i18n.ts | 6 +++- .../core/src/render3/instructions/shared.ts | 36 +++++++++++-------- packages/core/test/acceptance/i18n_spec.ts | 34 ++++++++++++++++++ 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 1fcea56815..4d4f4eaaad 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -18,7 +18,7 @@ import {bindingUpdated} from './bindings'; import {attachPatchData} from './context_discovery'; import {setDelayProjection} from './instructions/all'; import {attachI18nOpCodesDebug} from './instructions/lview_debug'; -import {TsickleIssue1009, allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, textBindingInternal} from './instructions/shared'; +import {TsickleIssue1009, allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal} from './instructions/shared'; import {LContainer, NATIVE} from './interfaces/container'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n'; import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from './interfaces/node'; @@ -1006,6 +1006,10 @@ function i18nAttributesFirstPass(lView: LView, tView: TView, index: number, valu const dataValue = tNode.inputs && tNode.inputs[attrName]; if (dataValue) { setInputsForProperty(lView, dataValue, value); + if (ngDevMode) { + const element = getNativeByIndex(previousElementIndex, lView) as RElement | RComment; + setNgReflectProperties(lView, element, tNode.type, dataValue, value); + } } } } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index e1d43ebeb6..c4706c9a54 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -889,20 +889,7 @@ export function elementPropertyInternal( setInputsForProperty(lView, dataValue, value); if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET); if (ngDevMode) { - if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) { - /** - * dataValue is an array containing runtime input or output names for the directives: - * i+0: directive instance index - * i+1: publicName - * i+2: privateName - * - * e.g. [0, 'change', 'change-minified'] - * we want to set the reflected property with the privateName: dataValue[i+2] - */ - for (let i = 0; i < dataValue.length; i += 3) { - setNgReflectProperty(lView, element, tNode.type, dataValue[i + 2] as string, value); - } - } + setNgReflectProperties(lView, element, tNode.type, dataValue, value); } } else if (tNode.type === TNodeType.Element) { propName = mapPropName(propName); @@ -945,7 +932,7 @@ function markDirtyIfOnPush(lView: LView, viewIndex: number): void { } } -export function setNgReflectProperty( +function setNgReflectProperty( lView: LView, element: RElement | RComment, type: TNodeType, attrName: string, value: any) { const renderer = lView[RENDERER]; attrName = normalizeDebugBindingName(attrName); @@ -969,6 +956,25 @@ export function setNgReflectProperty( } } +export function setNgReflectProperties( + lView: LView, element: RElement | RComment, type: TNodeType, dataValue: PropertyAliasValue, + value: any) { + if (type === TNodeType.Element || type === TNodeType.Container) { + /** + * dataValue is an array containing runtime input or output names for the directives: + * i+0: directive instance index + * i+1: publicName + * i+2: privateName + * + * e.g. [0, 'change', 'change-minified'] + * we want to set the reflected property with the privateName: dataValue[i+2] + */ + for (let i = 0; i < dataValue.length; i += 3) { + setNgReflectProperty(lView, element, type, dataValue[i + 2] as string, value); + } + } +} + function validateProperty( hostView: LView, element: RElement | RComment, propName: string, tNode: TNode): boolean { // The property is considered valid if the element matches the schema, it exists on the element diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index fc7cd220e8..58469b704e 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -1171,6 +1171,40 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.detectChanges(); expect(fixture.nativeElement.firstChild.title).toEqual(`ANGULAR - value 1 - value 2 (fr)`); }); + + it('should create corresponding ng-reflect properties', () => { + @Component({ + selector: 'welcome', + template: '{{ messageText }}', + }) + class WelcomeComp { + @Input() messageText !: string; + } + + @Component({ + template: ` + + + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, WelcomeComp], + }); + loadTranslations({ + [computeMsgId('Hello')]: 'Bonjour', + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const comp = fixture.debugElement.query(By.css('welcome')); + expect(comp.attributes['messagetext']).toBe('Bonjour'); + expect(comp.attributes['ng-reflect-message-text']).toBe('Bonjour'); + }); }); it('should work with directives and host bindings', () => {