From 4da5e9a156868097d352f567c401528d2f945f47 Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 26 May 2019 11:45:45 +0200 Subject: [PATCH] fix(ivy): type checking - apply attribute to property name mapping (#30675) Some HTML attributes don't correspond to their DOM property name, in which case the runtime will apply the appropriate transformation when assigning a property using its attribute name. One example of this is the `for` attribute, for which the DOM property is named `htmlFor`. The type-checking machinery in ngtsc must also take this mapping into account, as it generates type-check code in which unclaimed property bindings are assigned to properties of (subtypes of) `HTMLElement`. Fixes #30607 Fixes FW-1327 PR Close #30675 --- .../src/ngtsc/typecheck/src/type_check_block.ts | 16 +++++++++++++++- .../typecheck/test/type_check_block_spec.ts | 5 +++++ packages/core/src/render3/instructions/shared.ts | 6 ++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index 7ee4cd8670..34ceffb1f0 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -287,6 +287,19 @@ class TcbDirectiveOp extends TcbOp { } } +/** + * Mapping between attributes names that don't correspond to their element property names. + * Note: this mapping has to be kept in sync with the equally named mapping in the runtime. + */ +const ATTR_TO_PROP: {[name: string]: string} = { + 'class': 'className', + 'for': 'htmlFor', + 'formaction': 'formAction', + 'innerHtml': 'innerHTML', + 'readonly': 'readOnly', + 'tabindex': 'tabIndex', +}; + /** * A `TcbOp` which generates code to check "unclaimed inputs" - bindings on an element which were * not attributed to any directive or component, and are instead processed against the HTML element @@ -324,7 +337,8 @@ class TcbUnclaimedInputsOp extends TcbOp { if (binding.type === BindingType.Property) { if (binding.name !== 'style' && binding.name !== 'class') { // A direct binding to a property. - const prop = ts.createPropertyAccess(elId, binding.name); + const propertyName = ATTR_TO_PROP[binding.name] || binding.name; + const prop = ts.createPropertyAccess(elId, propertyName); const assign = ts.createBinary(prop, ts.SyntaxKind.EqualsToken, expr); this.scope.addStatement(ts.createStatement(assign)); } else { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts index 84887b8c23..e17e4f1a02 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts @@ -40,6 +40,11 @@ describe('type check blocks', () => { expect(tcb(TEMPLATE)).toContain('ctx.a[ctx.b];'); }); + it('should translate unclaimed bindings to their property equivalent', () => { + const TEMPLATE = ``; + expect(tcb(TEMPLATE)).toContain('_t1.htmlFor = "test";'); + }); + it('should generate a forward element reference correctly', () => { const TEMPLATE = ` {{ i.value }} diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 948c3617d5..41ebfb15b2 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -765,8 +765,10 @@ export function generatePropertyAliases(tNode: TNode, direction: BindingDirectio } /** -* Mapping between attributes names that don't correspond to their element property names. -*/ + * Mapping between attributes names that don't correspond to their element property names. + * Note: this mapping has to be kept in sync with the equally named mapping in the template + * type-checking machinery of ngtsc. + */ const ATTR_TO_PROP: {[name: string]: string} = { 'class': 'className', 'for': 'htmlFor',