In Angular, why would you declare exportAs on an attribute directive, and what does a template gain from it?
answer
- a template-facing name
- #x="name" gets the instance
- no exportAs, no reference
- public members become the API
- comma-separated aliases
basics
~20 sDeclaring 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 linesimport {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
Recall that exportAs names the directive for #x="name", and the ngForm and ngModel examples.
Explain why a bare reference cannot pick a directive, and that without exportAs a template cannot reference it.
Design an exported directive's public surface deliberately and prefer outputs or queries when a reference would leak internals.
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