Sometimes you need to reach an element or a child component from your code: to focus an input, read a value, or call a method on a child. Angular gives you template reference variables for the template and queries for the class.
What you will learn
- Template reference variables with
#name ng-containerfor grouping without extra elementsviewChild,viewChildren,contentChildand the older@ViewChildandQueryList
Template reference variables
A #name on an element creates a variable you can use anywhere in the same template:
// src/app/refs/ref-demo.ts
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-ref-demo',
template: `
<input #box type="text" placeholder="Type here" />
<button type="button" (click)="show(box.value)">Show value</button>
<button type="button" (click)="box.focus()">Focus</button>
<p>You typed: {{ typed() }}</p>
`,
})
export class RefDemo {
protected readonly typed = signal('');
protected show(value: string): void {
this.typed.set(value);
}
}
box is the real HTMLInputElement, so box.value and box.focus() work. On a component, the variable is the component instance, and #form="ngForm" gives you a directive instance by its exportAs name. This is also how you avoid the EventTarget typing error: pass box.value instead of reading $event.target.
ng-container
<ng-container> is a grouping element that does not appear in the DOM. It is useful when you need a control-flow block or a directive without adding a wrapper div:
<ul>
@for (user of users(); track user.id) {
<ng-container>
<li>{{ user.name }}</li>
<li class="meta">{{ user.email }}</li>
</ng-container>
}
</ul>
Two li elements per user, and no extra tag between them. With @if and @for you rarely need it, but it is also used with ngTemplateOutlet and *ngComponentOutlet.
viewChild: reach into your own template
Use viewChild() to find an element or component in the component's own template. It returns a signal:
// src/app/refs/search-box.ts
import { Component, ElementRef, afterNextRender, viewChild } from '@angular/core';
@Component({
selector: 'app-search-box',
template: `
<label for="q">Search</label>
<input #query id="q" type="search" />
<button type="button" (click)="focus()">Focus the box</button>
`,
})
export class SearchBox {
// A signal holding the element, or undefined until it exists.
protected readonly query = viewChild.required<ElementRef<HTMLInputElement>>('query');
constructor() {
afterNextRender(() => this.focus()); // focus when the page first shows
}
protected focus(): void {
this.query().nativeElement.focus();
}
}
viewChild('query')looks for#query. Use.requiredwhen the element always exists.- The result is an
ElementReffor elements. ItsnativeElementis the real DOM node. - The signal is filled after the view is created, so read it in
afterNextRenderor later, not in the constructor body itself.
Call a method on a child component
// src/app/refs/counter-child.ts
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter-child',
template: `<p>Count: {{ count() }}</p>`,
})
export class CounterChild {
readonly count = signal(0);
increment(): void {
this.count.update((n) => n + 1);
}
}
// src/app/refs/query-demo.ts
import { Component, viewChild, viewChildren } from '@angular/core';
import { CounterChild } from './counter-child';
@Component({
selector: 'app-query-demo',
imports: [CounterChild],
template: `
<app-counter-child #first />
<app-counter-child />
<app-counter-child />
<button type="button" (click)="bumpFirst()">+1 first</button>
<button type="button" (click)="bumpAll()">+1 all</button>
`,
})
export class QueryDemo {
protected readonly first = viewChild.required(CounterChild); // first match by type
protected readonly all = viewChildren(CounterChild); // a signal of an array
protected bumpFirst(): void {
this.first().increment();
}
protected bumpAll(): void {
this.all().forEach((c) => c.increment());
}
}
viewChildren returns a signal with an array of all matches. It updates when matches are added or removed, for example inside @if or @for.
contentChild: reach into projected content
viewChild searches the component's own template. contentChild and contentChildren search the content the parent projected into <ng-content />:
// src/app/refs/tabs.ts
import { Component, contentChildren, input } from '@angular/core';
@Component({
selector: 'app-tab',
template: `<ng-content />`,
})
export class Tab {
readonly title = input.required<string>();
}
@Component({
selector: 'app-tabs',
template: `
<p>This container has {{ tabs().length }} tabs:</p>
<ul>
@for (tab of tabs(); track tab.title()) {
<li>{{ tab.title() }}</li>
}
</ul>
<ng-content />
`,
})
export class Tabs {
protected readonly tabs = contentChildren(Tab);
}
// src/app/refs/refs-page.ts
import { Component } from '@angular/core';
import { QueryDemo } from './query-demo';
import { RefDemo } from './ref-demo';
import { SearchBox } from './search-box';
import { Tab, Tabs } from './tabs';
@Component({
selector: 'app-refs-page',
imports: [QueryDemo, RefDemo, SearchBox, Tab, Tabs],
template: `
<app-ref-demo />
<app-search-box />
<app-query-demo />
<app-tabs>
<app-tab title="One">First</app-tab>
<app-tab title="Two">Second</app-tab>
</app-tabs>
`,
})
export class RefsPage {}
// src/app/app.ts
import { Component } from '@angular/core';
import { RefsPage } from './refs/refs-page';
@Component({
imports: [RefsPage],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
<app-refs-page />
</main>
The older decorators and QueryList
Older code uses decorators. They do the same job, but return plain values and a QueryList, and are filled before the ngAfterViewInit or ngAfterContentInit hook:
// src/app/refs/legacy-queries.ts
import { AfterViewInit, Component, ElementRef, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { CounterChild } from './counter-child';
@Component({
selector: 'app-legacy-queries',
imports: [CounterChild],
template: `
<input #box />
<app-counter-child />
<app-counter-child />
`,
})
export class LegacyQueries implements AfterViewInit {
@ViewChild('box') box!: ElementRef<HTMLInputElement>;
@ViewChildren(CounterChild) counters!: QueryList<CounterChild>;
ngAfterViewInit(): void {
this.box.nativeElement.focus();
console.log('counters:', this.counters.length);
// QueryList is observable-like: it emits when the list changes
this.counters.changes.subscribe((list) => console.log('now', list.length));
}
}
Migration: ng generate @angular/core:signal-queries converts decorators to signal queries.
- `@ViewChild('x')`: New:
viewChild('x') - `@ViewChildren(Type)`: New:
viewChildren(Type) - `@ContentChild`: New:
contentChild - `@ContentChildren`: New:
contentChildren - `QueryList`: New: A plain array in a signal
When to use queries (and when not to)
Prefer data flow with inputs, outputs and signals. Use queries for things data cannot do: focusing, measuring, scrolling, or calling an imperative method of a child like video.play().
Try it yourself
- Add a "Select all text" button that calls
select()on the input from aviewChild. - Make
Tabsshow only the content of the active tab, usingcontentChildren. - Use
@ifto remove one counter and watchall().lengthupdate.
Quick check
- How do you create a template reference variable?
- What is the difference between
viewChildandcontentChild? - What does
ng-containeradd to the DOM?
Next, continue with Angular Services and Dependency Injection Explained.