Skip to content

Commit f600f0f

Browse files
dylhunnthePunderWoman
authored andcommitted
refactor(compiler): Show an error when a property binding duplicates an i18n attribute (#54063)
Consider the following very quirky Angular template, which has both an i18n attribute binding and a property binding to `in`: ``` <cmp [in]="foo" in="bar" i18n-in /> ``` What would you expect the above template to do? `TemplateDefinitionBuilder` will emit the following Ivy instructions: ``` // Element constant attributes consts: () => { __i18nMsg__('bar', [], {}, {}) return [["in", i18n_0, __AttributeMarker.I18n__, "in"]]; } // ... function MyComponent_Template(rf, ctx) { if (rf & 1) { // Create mode i0.ɵɵelement(0, "cmp", 0); } } ``` This makes some sense -- we create a single element, and attach an i18n message to the `in` attribute. But is this actually correct? Notice that the property binding is completely missing! Indeed, Template Pipeline actually produces this code: ``` // Element constant attributes consts: () => { __i18nMsg__('bar', [], {}, {}) return [["in", i18n_0, __AttributeMarker.I18n__, "in"]]; } // ... function MyComponent_Template(rf, ctx) { if (rf & 1) { // Create mode i0.ɵɵelement(0, "cmp", 0); } else if (rf & 2) { // Update mode i0.ɵɵproperty("in", ctx.foo); } } ``` Aha! There's the property binding! Arguably, this is a bug in `TemplateDefinitionBuilder`, but after some discussion on Slack, we have decided to ban this practice in a future Angular version. For now, we allow Template Pipeline to have slightly different output, but print an error to warn the user of the issue. PR Close #54063
1 parent 8d230e1 commit f600f0f

1 file changed

Lines changed: 10 additions & 0 deletions

File tree

  • packages/compiler/src/template/pipeline/src

packages/compiler/src/template/pipeline/src/ingest.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,16 +895,26 @@ function ingestElementBindings(
895895
unit: ViewCompilationUnit, op: ir.ElementOpBase, element: t.Element): void {
896896
let bindings = new Array<ir.BindingOp|ir.ExtractedAttributeOp|null>();
897897

898+
let i18nAttributeBindingNames = new Set<string>();
899+
898900
for (const attr of element.attributes) {
899901
// Attribute literal bindings, such as `attr.foo="bar"`.
900902
const securityContext = domSchema.securityContext(element.name, attr.name, true);
901903
bindings.push(ir.createBindingOp(
902904
op.xref, ir.BindingKind.Attribute, attr.name,
903905
convertAstWithInterpolation(unit.job, attr.value, attr.i18n), null, securityContext, true,
904906
false, null, asMessage(attr.i18n), attr.sourceSpan));
907+
if (attr.i18n) {
908+
i18nAttributeBindingNames.add(attr.name);
909+
}
905910
}
906911

907912
for (const input of element.inputs) {
913+
if (i18nAttributeBindingNames.has(input.name)) {
914+
console.error(`On component ${unit.job.componentName}, the binding ${
915+
input
916+
.name} is both an i18n attribute and a property. You may want to remove the property binding. This will become a compilation error in future versions of Angular.`);
917+
}
908918
// All dynamic bindings (both attribute and property bindings).
909919
bindings.push(ir.createBindingOp(
910920
op.xref, BINDING_KINDS.get(input.type)!, input.name,

0 commit comments

Comments
 (0)