In Angular, how does an attribute directive's selector match elements, and why can [appHighlight]="color" both apply and configure it?
answer
- matched at compile time
- attributes and binding names count
- element, class, attribute, :not
- input shares the selector name
- missing import: inert or NG8002
basics
~20 sAngular 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 sThe 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 linesimport {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
Know that [appHighlight]="color" works because the input shares the selector's name, and that the directive must be imported.
Explain compile-time matching, which selector forms are supported, and why bound and static forms behave differently when the import is missing.
Debug an inert directive methodically: imports, selector, runtime-added attributes, and inputs read non-reactively, and use required inputs to fail fast.
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