{"record":{"id":"27a0d0c2b16f8b00","repo":"angular/angular","slug":"error-in-attribute-selector-attr-unescaped","errorCode":null,"errorMessage":"Error in attribute selector \"${attr}\". Unescaped \"$\" is not supported. Please escape with \"\\$\".","messagePattern":"Error in attribute selector \"(.+?)\"\\. Unescaped \"\\$\" is not supported\\. Please escape with \"\\\\\\$\"\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/compiler/src/directive_matching.ts","lineNumber":143,"sourceCode":"   *\n   * This is needed because `$` can have a special meaning in CSS selectors,\n   * but we might want to match an attribute that contains `$`.\n   * [MDN web link for more\n   * info](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).\n   * @param attr the attribute to unescape.\n   * @returns the unescaped string.\n   */\n  unescapeAttribute(attr: string): string {\n    let result = '';\n    let escaping = false;\n    for (let i = 0; i < attr.length; i++) {\n      const char = attr.charAt(i);\n      if (char === '\\\\') {\n        escaping = true;\n        continue;\n      }\n      if (char === '$' && !escaping) {\n        throw new Error(\n          `Error in attribute selector \"${attr}\". ` +\n            `Unescaped \"$\" is not supported. Please escape with \"\\\\$\".`,\n        );\n      }\n      escaping = false;\n      result += char;\n    }\n    return result;\n  }\n\n  /**\n   * Escape `$` sequences from the CSS attribute selector.\n   *\n   * This is needed because `$` can have a special meaning in CSS selectors,\n   * with this method we are escaping `$` with `\\$'.\n   * [MDN web link for more\n   * info](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).\n   * @param attr the attribute to escape.","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/angular/angular/blob/51cb07e98081ab7e4e84a9e0949266a8e19cca84/packages/compiler/src/directive_matching.ts#L125-L161","documentation":"unescapeAttribute runs over attribute names parsed from a selector and rejects a '$' that was not preceded by a backslash. Angular uses '$' internally when encoding special attribute prefixes (attr./style./class.), so a raw '$' in an attribute selector is ambiguous; source selectors must write '\\$' which this function unescapes.","triggerScenarios":"An attribute selector whose attribute name contains an unescaped dollar: '[data-test$]', '[foo$bar=\"v\"]' in a @Directive/@Component selector or ng-content select.","commonSituations":"Naming conventions that include '$' (observable-style names like count$, data$) leaking into attribute selector names; test attributes generated by tooling.","solutions":["Escape the dollar in the selector: '[data-test\\$]' (backslash-dollar inside the string literal).","Or rename the attribute to avoid '$' entirely — cleaner for attribute binding and template readability."],"exampleFix":"// before\n@Directive({selector: '[data-flag$]'})\n\n// after\n@Directive({selector: '[data-flag\\$]'}) // '\\$' unescapes to a literal '$' attribute","handlingStrategy":"validation","validationCode":"const hasUnescapedDollar = (selector: string): boolean =>\n  /\\[[^\\]]*(?<!\\\\)\\$/.test(selector); // '$' inside an attribute selector without a preceding '\\'\n\nfunction assertValidAttributeSelector(selector: string): void {\n  if (hasUnescapedDollar(selector)) {\n    throw new Error(`Attribute selector \"${selector}\" contains an unescaped '$'; write '\\\\$'`);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid '$' in attribute names consumed by Angular (skip observable-style suffixes like count$).","When '$' is required, always escape it in the selector string ('\\\\$') and cover it with a selector unit test."],"tags":["angular","css-selector","escaping","directive-selector"],"backgroundTag":"invalid-css-selector","analyzedSha":"51cb07e98081ab7e4e84a9e0949266a8e19cca84","analyzedAt":"2026-08-22T07:04:54.531Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}