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
This commit is contained in:
Andrew Kushnir 2019-10-03 16:47:40 -07:00 committed by Alex Rickabaugh
parent d18289fa9c
commit 90fb5d9f7a
3 changed files with 60 additions and 16 deletions

View File

@ -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);
}
}
}
}

View File

@ -889,20 +889,7 @@ export function elementPropertyInternal<T>(
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

View File

@ -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: `
<welcome
messageText="Hello"
i18n-messageText="Welcome message description">
</welcome>
`
})
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', () => {