skip to content

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%

answer

  1. a template-facing name
  2. #x="name" gets the instance
  3. no exportAs, no reference
  4. public members become the API
  5. comma-separated aliases

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.

solid answer

~40 s

`exportAs` in `@Directive` sets the name a template uses to grab the directive instance: with `exportAs: 'appHighlight'`, `<tr appHighlight #hl="appHighlight">` gives `hl` the instance, so a button can call `hl.toggle()` or show `hl.on()`. A bare `#hl` on the same element would give the DOM node or the host component instead, and a directive without `exportAs` cannot be referenced from a template. Built-ins follow this pattern: `ngModel`, `ngForm`, `routerLinkActive`. The value can list several comma-separated names. Declaring it makes the directive's public members part of its template API, so expose small, stable members, typically signals and methods, and keep internals `protected` or private.

code

ts · 13 lines
ts
import {Directive, signal} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  exportAs: 'appHighlight',
  host: {'[class.highlighted]': 'on()'},
})
export class Highlight {
  readonly on = signal(false);
  toggle(): void {
    this.on.update((v) => !v);
  }
}

go deeper

for a junior

Recall that exportAs names the directive for #x="name", and the ngForm and ngModel examples.

for a middle

Explain why a bare reference cannot pick a directive, and that without exportAs a template cannot reference it.

for a senior

Design an exported directive's public surface deliberately and prefer outputs or queries when a reference would leak internals.

for a principal

Treat exported directive members as a versioned API for shared libraries and review changes to them as breaking.

## What exportAs does `exportAs` is a `@Directive` (and `@Component`) option that gives the directive a **template-facing name**: ```ts @Directive({ selector: '[appHighlight]', exportAs: 'appHighlight', host: {'[class.highlighted]': 'on()'}, }) export class Highlight { readonly on = signal(false); toggle(): void { this.on.update((v) => !v); } } ``` A template can then capture the instance with a template reference variable whose value is that name: ```html <tr appHighlight #hl="appHighlight"> <td>{{ order.ref }}</td> <td><button type="button" (click)="hl.toggle()">{{ hl.on() ? 'Unmark' : 'Mark' }}</button></td> </tr> ``` ## Why it is needed An element can carry several directives, and possibly a component. A bare `#hl` therefore cannot mean "the directive"; it resolves to the host component if there is one, otherwise to the DOM element. `exportAs` is how a directive **opts in** to being referenced, and the name in `#hl="..."` says which directive the template wants. Without `exportAs`, there is no way for a template to reference that directive; a name that no directive on the element exports is a compile error. ## Where you meet it Angular's own directives use it for exactly this: | Directive | `exportAs` | Typical template use | |---|---|---| | `NgForm` | `ngForm` | `#f="ngForm"`, then `f.valid` | | `NgModel` | `ngModel` | `#email="ngModel"`, then `email.errors` | | `RouterLinkActive` | `routerLinkActive` | `#rla="routerLinkActive"`, then `rla.isActive` | In each case the directive keeps **state** that the surrounding markup wants to display or act on. ## Details worth knowing - **Several names**: the value may be a comma-separated list, such as `exportAs: 'appHighlight, highlight'`, and each name works in `#x="..."`. - **Per element**: each element gets its own directive instance, so in a list every row's `hl` is that row's directive. - **Public API**: everything public on the class becomes reachable from templates. Keep the surface deliberate: - expose read-only signals and intention-revealing methods; - mark internals `protected` or `private`; - treat renames of exported members as breaking changes for shared directives. - **Same name as the selector** is a common convention, so usage reads naturally. ## Bare reference versus exported reference On one element, the same `#` syntax can resolve to three different things, and `exportAs` is the only way to pick a directive: | Template | `hl` resolves to | |---|---| | `<tr appHighlight #hl>` | the `<tr>` DOM element | | `<app-row appHighlight #hl>` | the `AppRow` component instance | | `<tr appHighlight #hl="appHighlight">` | the `Highlight` directive instance | This is why interviewers ask about `exportAs` alongside forms: `#email` alone on an `ngModel` input is the `HTMLInputElement`, whose `errors` property does not exist, while `#email="ngModel"` is the `NgModel` directive that tracks validity. Mixing the two up is a classic source of "`email.errors` is undefined" questions. ## Testing an exported directive Because the exported members are called from templates, test them through a template: 1. write a tiny host component whose template applies the directive with `#hl="appHighlight"` and a button calling `hl.toggle()`; 2. click the button in the test and assert the host element gained the `highlighted` class; 3. keep a second test that only uses the selector, proving the directive also works when nobody exports it. ## When not to use it - If the template needs a value, not behaviour, an `output()` from the directive can often push it to the parent instead. - If component code needs the instance, a query such as `viewChild(Highlight)` finds it by type without `exportAs`. - Do not export a directive just to reach its host element; the reference already gives you the element when used without a value.

  • Does an Angular component class need exportAs to get a directive instance through a query?
    No. A query like `viewChild(Highlight)` or `viewChildren(Highlight)` locates directives by type. `exportAs` is only about template reference variables; it has no effect on type-based queries.
  • What should an Angular directive expose once it declares exportAs?
    A small, stable surface: read-only signals for state and methods named for intentions such as `toggle()` or `reset()`. Everything public becomes reachable from any template that exports it, so keep internals `protected` or `private` and treat renames as breaking for shared directives.

saying these in an interview costs you the question

  • Any directive can be referenced as #x="ClassName" without exportAs
  • A bare #x on an element returns its attribute directive
  • exportAs is required for queries to find the directive
  • exportAs can only declare a single name
  • exportAs renames the directive's selector