skip to content

In Angular, how does an attribute directive's selector match elements, and why can [appHighlight]="color" both apply and configure it?

level: middleimportance: should knowfreq 45%

answer

  1. matched at compile time
  2. attributes and binding names count
  3. element, class, attribute, :not
  4. input shares the selector name
  5. missing import: inert or NG8002

basics

~20 s

Angular matches directive selectors at compile time against each element's tag, static attributes, classes and binding names. An input with the same name as the attribute selector means [appHighlight]="color" satisfies the selector and sets that input in one step.

solid answer

~40 s

The compiler matches every directive in a component's `imports` against each template element, using the tag name, static attributes and classes, and also the names of property bindings. Selectors support element, class and attribute forms, `[attr=value]`, `:not()`, concatenation such as `button[appHighlight]` and comma lists, but no combinators, other pseudo-classes or namespaces. Because `[appHighlight]="color"` binds a name that matches `[appHighlight]`, the directive is applied, and since it has an input called `appHighlight` the value flows into it. Use `input('', {alias: 'appHighlight'})` if you want a clearer class-side name. If the directive is not imported, a bare attribute silently does nothing while the bound form fails with NG8002, `Can't bind to 'appHighlight' since it isn't a known property`. Setting the attribute at runtime never instantiates a directive.

code

ts · 9 lines
ts
import {Directive, input} from '@angular/core';

@Directive({
  selector: 'button[appHighlight]:not([disabled])',
  host: {'[style.outline-color]': 'color()'},
})
export class ButtonHighlight {
  readonly color = input.required<string>({alias: 'appHighlight'});
}

go deeper

for a junior

Know that [appHighlight]="color" works because the input shares the selector's name, and that the directive must be imported.

for a middle

Explain compile-time matching, which selector forms are supported, and why bound and static forms behave differently when the import is missing.

for a senior

Debug an inert directive methodically: imports, selector, runtime-added attributes, and inputs read non-reactively, and use required inputs to fail fast.

for a principal

Standardise selector conventions and required inputs across shared directives so misuse fails at compile time rather than silently.

## Matching happens in the compiler Angular decides which directives apply to which elements **when the template is compiled**, not at runtime. For each element, the compiler checks every directive the component makes available (its `imports`) and instantiates the ones whose `selector` matches. Two consequences: - adding the attribute later with `setAttribute('appHighlight', '')` never creates the directive; - a directive the component did not import is never considered, whatever the markup says. ## What a selector may contain Angular supports a subset of CSS selector syntax: | Form | Example | Matches | |---|---|---| | element | `app-card` | `<app-card>` | | attribute | `[appHighlight]` | any element with that attribute or binding | | attribute with value | `[type="reset"]` | an exact value only; no other operators | | class | `.menu-item` | elements with that class | | `:not()` | `[appHighlight]:not(textarea)` | narrows another selector | | concatenation | `button[appHighlight]` | both conditions on one element | | comma list | `[appHighlight], [appHover]` | any of the listed selectors | Not supported: descendant or child **combinators**, other pseudo-classes and pseudo-elements, and **namespaces**. ## Why [appHighlight]="color" works The matcher looks at the element's static attributes **and at the names of its property bindings**. So both of these match `[appHighlight]`: ```html <p appHighlight>static attribute, input receives ''</p> <p [appHighlight]="color">binding, input receives color</p> ``` The second form then needs somewhere to send the value. If the directive declares an input named `appHighlight`, the binding targets that input, and the one attribute does two jobs: **apply the directive** and **configure it**. That is the idiom the Angular docs use. If you prefer a descriptive name inside the class, alias the input: ```ts readonly color = input('', {alias: 'appHighlight'}); ``` ## More inputs on the same element A directive can declare several inputs, all bound on the host element alongside the selector: - `[defaultColor]="fallback()"` binds an expression; - `defaultColor="violet"` passes a **static string**, no brackets needed; - `input.required<string>()` makes the input mandatory; leaving it out is a compile error, `Required input ... must be specified`. ## When it silently does nothing The most common bug report with attribute directives is "my directive does nothing". Work through these in order: 1. **Not imported.** With standalone directives (the default since v19), each component must import the directive. A bare `appHighlight` attribute is then just an unknown HTML attribute and stays inert with no error. The bound form is caught: `Can't bind to 'appHighlight' since it isn't a known property of 'p'` (NG8002). 2. **Selector mismatch.** A typo in the attribute, a `:not()` that excludes the element, or a concatenated selector such as `button[appHighlight]` used on a `<div>`. 3. **Added at runtime.** Attributes set by code or by `[attr.appHighlight]` do not trigger matching; only static attributes, property and two-way binding names, and event binding names are considered. 4. **Input never read reactively.** With signal inputs, read them inside a `computed`, an effect or a host binding; reading once in the constructor sees only the initial value. ## Interview summary - Matching is **static**: selectors plus imports decide everything at compile time. - Binding names count as attributes for matching, which enables the **selector-named input** idiom. - Aliasing, static-string inputs and required inputs work exactly as for components.

  • In Angular, why does a bare appHighlight attribute raise no error when the directive is not imported, but [appHighlight]="c" does?
    An unknown plain attribute is valid HTML, so the compiler leaves it on the element as an attribute. A property binding must target a DOM property or an input of a matched directive; with no directive matched and no `appHighlight` DOM property, the compiler reports NG8002, `Can't bind to 'appHighlight' since it isn't a known property`.
  • Can an Angular directive be activated by adding its attribute to an element at runtime?
    No. Directive matching is done by the compiler from the template source. Setting the attribute with `Renderer2.setAttribute`, `nativeElement.setAttribute` or an `[attr.x]` binding changes the DOM only. To attach behaviour dynamically, render a template or component that already contains the directive, or compose it with `hostDirectives`.

saying these in an interview costs you the question

  • Angular matches directive selectors against the live DOM at runtime
  • Directive selectors support descendant combinators like CSS
  • A binding like [appHighlight] cannot also satisfy an attribute selector
  • A forgotten directive import always produces a compile error
  • The input name must differ from the selector name