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:
parent
d18289fa9c
commit
90fb5d9f7a
|
@ -18,7 +18,7 @@ import {bindingUpdated} from './bindings';
|
||||||
import {attachPatchData} from './context_discovery';
|
import {attachPatchData} from './context_discovery';
|
||||||
import {setDelayProjection} from './instructions/all';
|
import {setDelayProjection} from './instructions/all';
|
||||||
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
|
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 {LContainer, NATIVE} from './interfaces/container';
|
||||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n';
|
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';
|
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];
|
const dataValue = tNode.inputs && tNode.inputs[attrName];
|
||||||
if (dataValue) {
|
if (dataValue) {
|
||||||
setInputsForProperty(lView, dataValue, value);
|
setInputsForProperty(lView, dataValue, value);
|
||||||
|
if (ngDevMode) {
|
||||||
|
const element = getNativeByIndex(previousElementIndex, lView) as RElement | RComment;
|
||||||
|
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -889,20 +889,7 @@ export function elementPropertyInternal<T>(
|
||||||
setInputsForProperty(lView, dataValue, value);
|
setInputsForProperty(lView, dataValue, value);
|
||||||
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
||||||
if (ngDevMode) {
|
if (ngDevMode) {
|
||||||
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
|
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (tNode.type === TNodeType.Element) {
|
} else if (tNode.type === TNodeType.Element) {
|
||||||
propName = mapPropName(propName);
|
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) {
|
lView: LView, element: RElement | RComment, type: TNodeType, attrName: string, value: any) {
|
||||||
const renderer = lView[RENDERER];
|
const renderer = lView[RENDERER];
|
||||||
attrName = normalizeDebugBindingName(attrName);
|
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(
|
function validateProperty(
|
||||||
hostView: LView, element: RElement | RComment, propName: string, tNode: TNode): boolean {
|
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
|
// The property is considered valid if the element matches the schema, it exists on the element
|
||||||
|
|
|
@ -1171,6 +1171,40 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.nativeElement.firstChild.title).toEqual(`ANGULAR - value 1 - value 2 (fr)`);
|
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', () => {
|
it('should work with directives and host bindings', () => {
|
||||||
|
|
Loading…
Reference in New Issue