skip to content

Reference & Let Variables

#ref template reference variables, @let declarations and let-x template input variables, and the scope each lives in. Interviewers ask what a #ref points to and why @let is read-only.

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

explore

questions

5

In an Angular template, what does a #ref variable hold on a plain element, a component, an ng-template and with an exportAs name?

level: juniorimportance: must knowfreq 68%

answer

  1. depends on where it is declared
  2. DOM node for plain elements
  3. instance for components
  4. TemplateRef for ng-template
  5. #x="name" picks a directive

basics

~10 s

An Angular #ref holds the DOM element on a plain element, the component instance on a component's host element, a TemplateRef on an ng-template, and the directive instance when written #x="exportAsName".

solid answer

~40 s

A template reference variable, written `#name` on an element, is resolved by what it sits on. On a plain element such as `<input #search>` it is the DOM element itself, so `(click)="search.focus()"` works. On a component's host element, `<app-date-picker #start>` is the component instance, with its public members. On `<ng-template #row>` it is a `TemplateRef`, the fragment you can render elsewhere. With a value, `#f="ngForm"` or `#rla="routerLinkActive"`, it is the instance of the directive whose `exportAs` matches that name; a name no directive exports is the compile error `No directive found with exportAs`. A ref is visible to the view that declares it and to the views nested inside it.

code

html · 7 lines
html
<form #f="ngForm" (ngSubmit)="save(f.value)">
  <input name="email" ngModel required #email="ngModel" />
  @if (email.touched && email.invalid) {
    <p>Email is required.</p>
  }
  <button [disabled]="f.invalid">Save</button>
</form>

go deeper

for a junior

Recall the four targets: DOM element, component instance, TemplateRef, and a directive picked by its exportAs name, plus the focus-a-field example.

for a middle

Explain why exportAs is needed when several directives share an element, and what compile error a wrong name gives.

for a senior

Judge when a ref is fine for template-local wiring and when coupling to a component's API or reading DOM snapshots should become a query or signal.

for a principal

Set a team norm: refs for local UI glue only, public component APIs kept small, and state never smuggled through DOM snapshots.

## What a template reference variable is A **template reference variable** is a name declared on an element with a hash prefix, `#name` (the long form `ref-name` also parses). It gives the rest of the template a way to reach something that exists at that spot: a DOM node, a component, a directive or a template fragment. It is a template-only name: the component class does not see it unless a query reads it. The same syntax yields **different kinds of values** depending on where it is declared: | Declared on | Example | The variable holds | |---|---|---| | a plain HTML element (or a custom element) | `<input #search>` | the DOM element (`HTMLInputElement`) | | an Angular component's host element | `<app-date-picker #start />` | the component instance | | an `<ng-template>` | `<ng-template #emptyState>` | a `TemplateRef` for that fragment | | any element, with a value | `<form #f="ngForm">` | the directive instance whose `exportAs` is `ngForm` | ## Plain elements: reaching the DOM The everyday use is calling a DOM API from an event handler without writing any class code. Focusing a search box from a button: ```ts import {Component} from '@angular/core'; @Component({ selector: 'app-search-bar', template: ` <button type="button" (click)="search.focus()">Search</button> <input #search type="search" placeholder="Find a product" /> `, }) export class SearchBar {} ``` Two details make this work: - the reference is resolved for the **whole view**, so the button may appear before the input it refers to; - the handler receives the real `HTMLInputElement`, so with strict template checks `search.focus()` is type-checked against the DOM typings. ## Components: reaching the instance When the element is a component's host, the variable is the **component instance**, not the host DOM node. That lets a sibling call a public method or read a public signal: `<app-date-picker #start />` then `<button (click)="start.clear()">`. It couples the template to that component's public API, so keep such calls to small, stable members. ## ng-template: a fragment, not an element `<ng-template>` renders nothing by itself. A reference on it is a **`TemplateRef`**, which other constructs can render later, for example as an `@if` fallback passed to a component input or through an outlet. Reading `.value` or calling DOM methods on it is a type error. ## exportAs: choosing a directive An element can carry several directives, so a bare `#x` never means "the directive". To get a directive instance you name it with the value the directive declares in `exportAs`: 1. `<form #f="ngForm">`: the `NgForm` directive, so `f.valid` and `f.value` work. 2. `<input name="email" ngModel #email="ngModel">`: the `NgModel` directive, so `email.errors` and `email.touched` work. 3. `<a routerLink="/orders" routerLinkActive="active" #rla="routerLinkActive">`: `rla.isActive`. If no directive on the element exports that name, compilation fails with `No directive found with exportAs '...'`. A directive that declares no `exportAs` cannot be referenced this way at all. ## Rules that come up in interviews - **A handle, not state**: a reference names a target for reading and calling; it is not a variable to store values in. - **One name per element**: declaring `#x` twice on the same element is a compile error (`Reference "#x" is defined more than once`); the docs also advise defining each name only once per template so the value stays predictable. - **Scoped**: a reference is visible in its own view and in nested views (inside `@if`, `@for`, `<ng-template>`), not in the parent template. - **Queries use the same names**: `viewChild('search')` can find an element by its reference name, which is the bridge from template to class. ## When not to use one A reference is a shortcut for **template-local** interaction. If the class needs the element, use a query; if the value is application state, keep it in a signal. A form value read through `#input.value` is a snapshot, not a binding, and it does not drive change detection.

  • In Angular, what does #picker hold on <app-date-picker #picker> when the element also carries an attribute directive?
    Still the component instance. A bare `#picker` on a component's host element always resolves to the component. To reach the attribute directive instead, write `#d="exportName"` using the name that directive declares in `exportAs`; without an `exportAs`, the directive cannot be referenced from the template.
  • Can the component class read an Angular #ref variable directly?
    Not directly: the name exists only in the template. The class reaches it through a view query such as `viewChild('search')`, which can locate an element by its reference name and return an `ElementRef`, a component or a `TemplateRef` depending on the target.

saying these in an interview costs you the question

  • #ref on a component always gives the host DOM element
  • A bare #ref picks whichever directive is on the element
  • #ref on an ng-template refers to a comment DOM node
  • Any directive can be referenced by its class name
  • A #ref on a plain element holds the element's current value
open as a page

In Angular templates, what does an @let declaration do, why can it not be reassigned, and where is it visible?

level: middleimportance: must knowfreq 55%

basics

~20 s

Angular's @let name = expression; gives a template expression a name that stays in sync like a binding. It cannot be assigned to, and it is visible after its declaration in its own view and nested views, not in parents or siblings.

open as a page

In Angular templates, why can a #ref declared inside an @if or @for block not be read from outside that block?

level: middleimportance: should knowfreq 40%

basics

~20 s

Angular's @if, @for, @defer and ng-template bodies are separate embedded views; a #ref declared inside one is visible there and in views nested deeper, but name lookup only walks outward, so the parent template cannot see it.

open as a page

An Angular template shows [object HTMLTextAreaElement] where a component's note text should be; how can template variable shadowing cause this, and how do you fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Angular resolves template names before component members, so a #note on a textarea shadows the component's note property and {{ note }} prints the element. Rename the reference, or write this.note to read the member explicitly.

open as a page

In an Angular <ng-template>, what do let-item and let-i="index" declare, and how do they differ from #ref and @let?

level: middleimportance: nice to knowfreq 35%

basics

~10 s

On an Angular <ng-template>, let-item binds the context's $implicit value and let-i="index" its index property: read-only inputs supplied by the renderer, unlike #ref (a template target) or @let (a named expression).

open as a page