diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js index bfdc9627560e..9f9dbd1f2e52 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js @@ -308,7 +308,7 @@ import * as i0 from "@angular/core"; export class MyComponent { expr = true; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); - static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class.text-primary/80": "expr", "class.data-active:text-green-300/80": "expr", "class.data-[size='large'": "expr" } }, ngImport: i0, template: ``, isInline: true }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class.text-primary/80": "expr", "class.data-active:text-green-300/80": "expr", "class.data-[size='large']:p-8": "expr" } }, ngImport: i0, template: ``, isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{ type: Component, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js index def9a9d5a7a8..a683e16b8905 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js @@ -3,7 +3,7 @@ $r3$.ɵɵdefineComponent({ hostVars: 6, hostBindings: function MyComponent_HostBindings(rf, ctx) { if (rf & 2) { - $r3$.ɵɵclassProp("text-primary/80", ctx.expr)("data-active:text-green-300/80", ctx.expr)("data-[size='large'", ctx.expr); + $r3$.ɵɵclassProp("text-primary/80", ctx.expr)("data-active:text-green-300/80", ctx.expr)("data-[size='large']:p-8", ctx.expr); } }, … diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 9c3eb81105f3..e076242c62cf 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -514,38 +514,42 @@ function createHostBindingsFunction( return emitHostBindingFunction(hostJob); } -const HOST_REG_EXP = /^(?:\[([^\]]+)\])|(?:\(([^\)]+)\))$/; -// Represents the groups in the above regex. -const enum HostBindingGroup { - // group 1: "prop" from "[prop]", or "attr.role" from "[attr.role]", or @anim from [@anim] - Binding = 1, - - // group 2: "event" from "(event)" - Event = 2, -} - // Defines Host Bindings structure that contains attributes, listeners, and properties, // parsed from the `host` object defined for a Type. export interface ParsedHostBindings { - attributes: {[key: string]: o.Expression}; - listeners: {[key: string]: string}; - properties: {[key: string]: string}; + attributes: Record; + listeners: Record; + properties: Record; specialAttributes: {styleAttr?: string; classAttr?: string}; } export function parseHostBindings(host: { [key: string]: string | o.Expression; }): ParsedHostBindings { - const attributes: {[key: string]: o.Expression} = {}; - const listeners: {[key: string]: string} = {}; - const properties: {[key: string]: string} = {}; + const attributes: Record = {}; + const listeners: Record = {}; + const properties: Record = {}; const specialAttributes: {styleAttr?: string; classAttr?: string} = {}; for (const key of Object.keys(host)) { const value = host[key]; - const matches = key.match(HOST_REG_EXP); - if (matches === null) { + if (key.startsWith('(') && key.endsWith(')')) { + if (typeof value !== 'string') { + // TODO(alxhub): make this a diagnostic. + throw new Error(`Event binding must be string`); + } + listeners[key.slice(1, -1)] = value; + } else if (key.startsWith('[') && key.endsWith(']')) { + if (typeof value !== 'string') { + // TODO(alxhub): make this a diagnostic. + throw new Error(`Property binding must be string`); + } + // synthetic properties (the ones that have a `@` as a prefix) + // are still treated the same as regular properties. Therefore + // there is no point in storing them in a separate map. + properties[key.slice(1, -1)] = value; + } else { switch (key) { case 'class': if (typeof value !== 'string') { @@ -568,21 +572,6 @@ export function parseHostBindings(host: { attributes[key] = value; } } - } else if (matches[HostBindingGroup.Binding] != null) { - if (typeof value !== 'string') { - // TODO(alxhub): make this a diagnostic. - throw new Error(`Property binding must be string`); - } - // synthetic properties (the ones that have a `@` as a prefix) - // are still treated the same as regular properties. Therefore - // there is no point in storing them in a separate map. - properties[matches[HostBindingGroup.Binding]] = value; - } else if (matches[HostBindingGroup.Event] != null) { - if (typeof value !== 'string') { - // TODO(alxhub): make this a diagnostic. - throw new Error(`Event binding must be string`); - } - listeners[matches[HostBindingGroup.Event]] = value; } }