In Angular, what is an attribute directive, and how would you build and apply an appHighlight directive that highlights an element on hover?
answer
- behaviour without its own template
- @Directive with a bracket selector
- import it where it is used
- input named like the selector
- host events toggle the style
basics
~20 sAn Angular attribute directive is a class decorated with @Directive and an attribute selector such as [appHighlight]; it adds behaviour to an existing element or component. Import it into the using component, react to host events and set styles via host bindings or ElementRef.
solid answer
~40 sAn attribute directive changes the look or behaviour of an element it is placed on, without a template of its own; components are directives that do have one. You declare it with `@Directive({selector: '[appHighlight]'})`: the square brackets make it an attribute selector, and an app prefix avoids collisions. Since v19 directives are standalone by default, so the using component lists it in `imports`. For hover highlighting, listen to `mouseenter` and `mouseleave` on the host (through the `host` metadata, which the docs prefer over `@HostListener`) and either set a host style binding from a signal or write `backgroundColor` through the injected `ElementRef`. An `input()` named `appHighlight` lets `[appHighlight]="color"` apply the directive and pass the colour in one attribute.
code
ts · 15 linesimport {Directive, input, signal} from '@angular/core';
@Directive({
selector: '[appHighlight]',
host: {
'(mouseenter)': 'hovered.set(true)',
'(mouseleave)': 'hovered.set(false)',
'[style.background-color]': 'hovered() ? appHighlight() || defaultColor() : null',
},
})
export class Highlight {
readonly appHighlight = input('');
readonly defaultColor = input('yellow');
protected readonly hovered = signal(false);
}go deeper
Recall @Directive, the bracket selector, importing it where used, and the hover highlight example with an input named after the selector.
Contrast the declarative host binding with ElementRef writes, and explain static versus bound input values on the same element.
Argue when a directive is the right reuse unit versus plain bindings or a wrapper component, and keep it signal-driven so OnPush and zoneless work.
Curate a small set of shared directives with a consistent prefix and review rules, rather than one-off directives for every styling need.
## What an attribute directive is Angular has two kinds of directives that you write yourself: - **Components**: directives with a template, which render their own DOM. - **Attribute directives**: directives **without** a template. They attach to an element that already exists, native or component, and change its appearance or behaviour. (Structural directives, which add and remove DOM through `<ng-template>`, are a third kind with their own mechanics.) The Angular docs frame the choice clearly: for a one-off change to a single element's classes, styles, properties or events, use **template bindings** directly. Write an attribute directive when you want to package that behaviour into a **reusable unit** you can apply to any element. ## Building appHighlight ```ts import {Directive, input, signal} from '@angular/core'; @Directive({ selector: '[appHighlight]', host: { '(mouseenter)': 'hovered.set(true)', '(mouseleave)': 'hovered.set(false)', '[style.background-color]': 'hovered() ? appHighlight() || defaultColor() : null', }, }) export class Highlight { readonly appHighlight = input(''); readonly defaultColor = input('yellow'); protected readonly hovered = signal(false); } ``` Piece by piece: 1. **`@Directive`** marks the class; there is no `template`. 2. **`selector: '[appHighlight]'`** is an attribute selector: any element carrying the `appHighlight` attribute, or a binding to it, gets an instance. The style guide recommends a camelCase attribute name with your app's prefix. 3. **`host`** binds the host element's `mouseenter` and `mouseleave` events and its `background-color` style. The docs recommend the `host` property over the `@HostListener` / `@HostBinding` decorators, which remain for backwards compatibility. 4. **`input()`** declares inputs exactly as components do. Naming one after the selector lets a single attribute both apply and configure the directive. 5. A **signal** holds the hover state, so the host style binding updates when it changes, which also works with the `OnPush` default of v22 and zoneless change detection. ## The imperative variant with ElementRef The official tutorial version injects the host element and writes the style directly: ```ts private el = inject(ElementRef); private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; } ``` It works in the browser, but it is imperative DOM access. The declarative host binding is easier to test, keeps Angular in charge of the DOM, and needs no DOM on the server. Reserve `ElementRef` for things bindings cannot express, such as focus or measuring. ## Applying it Directives are **standalone by default since v19**, so the component that uses one imports it: ```ts @Component({ selector: 'app-price-list', imports: [Highlight], template: ` <p appHighlight>Default colour</p> <p [appHighlight]="accent()" defaultColor="lightgray">Bound colour</p> `, }) export class PriceList { readonly accent = signal('lightblue'); } ``` - `appHighlight` alone applies the directive with an empty input, so the default colour is used. - `[appHighlight]="accent()"` applies it and binds the input. - `defaultColor="lightgray"` passes a **static string** to an input without brackets. Angular creates **one directive instance per matching element**. ## Testing it Attribute directives are tested through a small host component, because they have no template of their own: 1. declare a test host whose template is `<p appHighlight defaultColor="yellow">text</p>` and import `Highlight`; 2. dispatch `mouseenter` on the paragraph and let change detection run; 3. assert the element's `style.backgroundColor`, then dispatch `mouseleave` and assert it is cleared. A declarative host binding makes step 3 deterministic: the style is derived from the `hovered` signal, so there is no stale imperative state to reset between tests. ## Common interview follow-ups | Question | Short answer | |---|---| | Directive or component? | No own template: directive. Own DOM to render: component. | | Why the prefix? | Avoids clashing with native attributes and third-party libraries. | | Can several directives sit on one element? | Yes; each matching directive gets its own instance. | | Can you apply one to a component's host? | Yes, it attaches to the component's host element. | | What if you forget to import it? | A plain attribute stays inert; a bound one fails to compile. |
- In Angular, what is the difference between an attribute directive and a component?A component is a directive with a template: it renders its own DOM inside its host element. An attribute directive has no template; it only adds behaviour to the element it matches, such as styles, classes, listeners or ARIA state. Several attribute directives can share an element, while an element can be the host of at most one component.
- Why does the Angular documentation prefer the host property over @HostListener in a directive like appHighlight?The `host` metadata keeps all host bindings and listeners in one declarative place, and the docs state that `@HostBinding` and `@HostListener` exist only for backwards compatibility. Both compile to the same kind of host bindings; the preference is about consistency with current Angular style.
saying these in an interview costs you the question
- An attribute directive needs a template like a component
- Directives are active everywhere once declared, without importing them
- Directive inputs need a different API from component inputs
- Only one directive can be applied to an element
- @HostListener is the recommended way to listen on the host