skip to content

Directives & Pipes

Attribute and structural directives, host bindings, hostDirectives composition, ng-template outlets, and built-in and custom pipes. Interviewers probe microsyntax desugaring and pipe purity.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

In Angular, what is an attribute directive, and how would you build and apply an appHighlight directive that highlights an element on hover?

level: juniorimportance: must knowfreq 70%

answer

  1. behaviour without its own template
  2. @Directive with a bracket selector
  3. import it where it is used
  4. input named like the selector
  5. host events toggle the style

basics

~20 s

An 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 s

An 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 lines
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);
}

go deeper

for a junior

Recall @Directive, the bracket selector, importing it where used, and the hover highlight example with an input named after the selector.

for a middle

Contrast the declarative host binding with ElementRef writes, and explain static versus bound input values on the same element.

for a senior

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.

for a principal

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
open as a page

In an Angular template, how do you format an invoice's amount, issue date and quantity with built-in pipes, including arguments and chaining?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Apply the currency, date and number pipes with |, e.g. amount | currency:'EUR', issuedOn | date:'longDate', qty | number:'1.0-0'. Arguments follow colons in order, and chained pipes run left to right.

open as a page

How do you write a custom Angular pipe with @Pipe and PipeTransform, give it an extra argument, and use it in a standalone component?

level: juniorimportance: must knowfreq 62%

basics

~10 s

Decorate a class with @Pipe({ name: 'truncate' }), implement PipeTransform's transform(value, ...args), and add the class to the standalone component's imports. Extra transform parameters become colon-separated arguments, as in text | truncate: 40.

open as a page

In Angular, what is the difference between <ng-template> and <ng-container>, and what does each leave in the rendered DOM?

level: juniorimportance: must knowfreq 68%

basics

~20 s

<ng-template> declares a fragment that Angular compiles but renders only when an outlet or code stamps it; <ng-container> renders its children immediately without adding a wrapper element. Neither becomes an HTML element itself; each is represented by a comment node.

open as a page

What does the asterisk prefix on an Angular structural directive, as in `<li *ngIf="user as u">`, desugar to at compile time?

level: juniorimportance: must knowfreq 60%

basics

~10 s

The Angular compiler wraps the element in an ng-template that carries only the structural directive and its microsyntax bindings; the directive then renders that template, or not, through TemplateRef and ViewContainerRef.

open as a page

In an Angular directive, when should you use ElementRef.nativeElement, when Renderer2, and when neither?

level: middleimportance: must knowfreq 60%

basics

~20 s

In Angular, prefer host bindings over both. Use ElementRef.nativeElement for focus, measuring or observers, inside render callbacks. Use Renderer2 when created elements need component style encapsulation or animation hooks; otherwise it is equivalent to native DOM APIs.

open as a page

In Angular, what is the difference between a pure and an impure pipe, and why does a pure pipe miss an item pushed into an array?

level: middleimportance: must knowfreq 70%

basics

~20 s

A pure pipe (the default) re-runs only when its value or an argument changes by Object.is, so pushing into the same array is invisible. An impure pipe (pure: false) runs on every check of its view.

open as a page

In Angular, what is the difference between the `host` metadata object and the `@HostBinding`/`@HostListener` decorators, and which should new code use?

level: middleimportance: must knowfreq 55%

basics

~20 s

Both bind properties, attributes, classes, styles and events on a directive's own element; the host object declares them in @Component or @Directive metadata, the decorators on class members. Angular's docs recommend the host object and keep the decorators only for backwards compatibility.

open as a page

In Angular, how do you pass values into a template rendered by NgTemplateOutlet, and what does the $implicit context key do?

level: middleimportance: must knowfreq 58%

basics

~10 s

Bind an object to ngTemplateOutletContext (or write context: in the * form); the template reads its keys with let-name="key". $implicit is the default key: a let- declaration with no value receives it.

open as a page

How would you write an Angular `*appHasRole` structural directive that renders its content only when the current user holds a given role?

level: middleimportance: must knowfreq 55%

basics

~10 s

Inject TemplateRef and ViewContainerRef, read a role input named after the selector, and call createEmbeddedView when the role is held or clear when it is not, guarding so the view is never created twice.

open as a page

In Angular, what happens when you list a directive in a component's `hostDirectives` array, and can templates bind its inputs by default?

level: juniorimportance: should knowfreq 38%

basics

~20 s

Angular creates an instance of that directive on the component's host element whenever the component is used, and its host bindings and listeners apply there. Its inputs and outputs are not part of the component's API unless listed in the entry.

open as a page

In an Angular directive's `host` object, how do you bind a CSS class, an ARIA attribute and an inline style on the host element?

level: juniorimportance: should knowfreq 50%

basics

~20 s

Use template-style keys in the host object: '[class.name]' toggles a class, '[attr.name]' sets or removes an attribute (ARIA names also work without attr. since v20.2), '[style.prop.unit]' sets a style, and a plain key sets a static attribute.

open as a page

Why does Angular reject `*ngIf` and `*ngFor` on the same element, and how do you apply both structural directives?

level: juniorimportance: should knowfreq 48%

basics

~20 s

Each asterisk expands into an ng-template around the element, and one element can only have one such wrapper, so the compiler rejects a second starred directive; nest them, putting the outer one on an ng-container.

open as a page

In Angular, when does NgClass or NgStyle still beat a plain [class] or [style] binding, and when should the binding win?

level: middleimportance: should knowfreq 55%

basics

~20 s

Angular's style guide prefers [class] and [style] bindings: no import and less overhead. NgClass still wins when an object key holds several space-separated classes or when the bound object is mutated in place, because [class] compares objects with ===.

open as a page

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%

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.

open as a page

In Angular, in what order does KeyValuePipe return an object's entries, and how do you keep the original order instead?

level: middleimportance: should knowfreq 36%

basics

~10 s

KeyValuePipe sorts entries by key by default, strings by code unit and numbers numerically. Pass a compare function as its argument to change that, or null (since v19) to keep the input's natural order.

open as a page

In an Angular template, how is {{ discounted ? net : gross | currency }} parsed, and where does the pipe rank against ?? and ternaries?

level: middleimportance: should knowfreq 42%

basics

~20 s

It parses as discounted ? net : (gross | currency), so only gross is formatted. The pipe binds more loosely than +, &&, || and ?? but more tightly than the ternary, so wrap the conditional in parentheses.

open as a page

In Angular, in what order are host directives and their host component constructed, given inputs and applied host bindings, and what does that order imply?

level: middleimportance: should knowfreq 30%

basics

~20 s

Host directives run before the component or directive that declares them in every phase: they are constructed first, receive inputs and ngOnInit first, and apply host bindings first. The host therefore applies its host bindings last and can override theirs.

open as a page

In Angular, how do you expose and alias a host directive's inputs and outputs on a component, and which mapping mistakes does the compiler reject?

level: middleimportance: should knowfreq 32%

basics

~20 s

Use the object form of a hostDirectives entry and list names in inputs and outputs, writing 'text: hint' to expose Tooltip's text as hint. A required host directive input must be exposed, and names must exist and not clash with another binding.

open as a page

In an Angular template, how does {{ formatPrice(item) }} differ from {{ item.price | price }} in when your code actually runs?

level: middleimportance: should knowfreq 50%

basics

~20 s

A method call in a binding runs every time the view is checked, because Angular cannot know its dependencies. A pure pipe runs only when its value or arguments change, reusing its previous result otherwise.

open as a page

How would you write an Angular click-outside-to-close directive using a `document:click` host listener, and does it need manual listener cleanup?

level: middleimportance: should knowfreq 45%

basics

~20 s

Declare '(document:click)': 'onClick($event)' in the directive's host object, inject ElementRef, and emit an output when the host element does not contain event.target. Angular removes the document listener when the directive is destroyed, so no manual cleanup is needed.

open as a page

In Angular's structural-directive microsyntax, how do `let`, `as` and keys like `of` or `trackBy` map onto the directive's inputs and template variables?

level: middleimportance: should knowfreq 38%

basics

~20 s

A naked first expression binds to the input named after the selector; other keys get the selector prefix and a capital letter, so of becomes ngForOf; let x reads $implicit, and as or let x = y read named context properties.

open as a page

An Angular appHighlight directive clears its highlight on outside clicks using Renderer2.listen on document, and memory grows as rows come and go; what leaks and how do you fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Renderer2.listen on document registers a listener that outlives each directive, and its closure keeps the destroyed directive and its host element reachable. Keep the returned unlisten function and call it from DestroyRef.onDestroy, or use a declarative document listener that Angular removes for you.

open as a page

An Angular invoice's due date shows one day early for some users; how do DatePipe's input parsing and time-zone argument explain it, and how do you fix it?

level: seniorimportance: should knowfreq 32%

basics

~20 s

An instant such as UTC midnight is formatted in each user's local zone by default, so western users see the previous day. Send a date-only string, or pass a zone argument or DATE_PIPE_DEFAULT_OPTIONS timezone such as 'UTC'.

open as a page

When would you compose Angular behaviours with `hostDirectives` instead of extending a base component or directive class, and how do the two differ?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Compose with hostDirectives to attach several independent behaviours, each with its own instance, state and opt-in API; inherit when a component is a specialised version of one base and should share its whole API.

open as a page

A custom Angular timeAgo pipe keeps showing '3 minutes ago' long after; why does it never update, and how would you design it to refresh correctly?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The pure pipe reads Date.now() internally, but its only input never changes, so transform never re-runs. Pass a shared now signal as an argument, updated by one timer, so the pure pipe re-runs when time moves.

open as a page

A table renders 500 rows, each with an Angular directive holding a `(document:click)` host listener, and every click now feels slow; why, and how would you fix it?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Each instance registers its own document listener, so one click runs 500 handlers, each marking its view and ancestors dirty and scheduling change detection. Mount the directive only on the open menu, or share one listener.

open as a page

How would you let consumers of an Angular data-table component supply their own cell template, and whose data and providers does that template see?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Accept a TemplateRef (input or content query) and render it per row with NgTemplateOutlet, passing the row as $implicit. The template still evaluates against, and by default injects from, the consumer that declared it.

open as a page

How do `ngTemplateContextGuard` and `ngTemplateGuard_` let Angular's template type checker type a custom structural directive's context and narrow its input?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Both are static members read only by Angular's template type checker: ngTemplateContextGuard is a type predicate that gives the template's let variables a real context type, and ngTemplateGuard_<input> narrows the bound expression inside the template.

open as a page

In Angular, why would you declare exportAs on an attribute directive, and what does a template gain from it?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Declaring exportAs on an Angular directive gives it a name that templates can use as #x="name" to get the directive instance and call its public members; without exportAs a template cannot reference the directive at all.

open as a page

showing 1–30 of 35