Skip to content
Angular ng components 4 min read

ViewChild & ContentChild

Angular components often need a direct handle on something living inside them — a DOM element to focus, a child component instance to call a method on, or projected content to inspect. Angular provides two families of queries for this: view queries (@ViewChild / viewChild()) that reach into a component’s own template, and content queries (@ContentChild / contentChild()) that reach into content projected through <ng-content>. Modern Angular (17+) adds signal-based query functions that are reactive, type-safe, and far less error-prone than the decorator timing rules of the past.

View queries vs content queries

The distinction comes down to where the queried element lives relative to the component doing the querying.

QueryWhat it findsDecoratorSignal function
Single view childAn element/directive/component in this component’s template@ViewChildviewChild()
Multiple view childrenAll matches in this template@ViewChildrenviewChildren()
Single content childA single projected element passed via <ng-content>@ContentChildcontentChild()
Multiple content childrenAll projected matches@ContentChildrencontentChildren()

View queries see what the component renders; content queries see what a parent projects into it. If your element sits between <ng-content> tags supplied by a consumer, you need a content query.

Signal queries (the modern default)

Signal queries are the recommended approach in Angular 17.3+. They return a signal you read by calling it, integrate with effect() and computed(), and have predictable availability tied to the rendering lifecycle.

import { Component, ElementRef, viewChild, effect } from '@angular/core';

@Component({
  selector: 'app-search-box',
  standalone: true,
  template: `
    <input #queryInput type="search" placeholder="Search…" />
    <button (click)="focusInput()">Focus</button>
  `,
})
export class SearchBoxComponent {
  // Reactive, read by calling the signal.
  private queryInput = viewChild<ElementRef<HTMLInputElement>>('queryInput');

  constructor() {
    effect(() => {
      const el = this.queryInput();
      console.log('Input available:', !!el);
    });
  }

  focusInput(): void {
    this.queryInput()?.nativeElement.focus();
  }
}

Output:

Input available: true

Required queries

When an element is guaranteed to exist, use viewChild.required() to drop the undefined from the type. Reading it before it resolves throws a clear runtime error instead of silently returning undefined.

import { Component, ElementRef, viewChild } from '@angular/core';

@Component({
  selector: 'app-canvas',
  standalone: true,
  template: `<canvas #surface width="320" height="200"></canvas>`,
})
export class CanvasComponent {
  // Type is Signal<ElementRef<HTMLCanvasElement>> — no undefined.
  surface = viewChild.required<ElementRef<HTMLCanvasElement>>('surface');

  draw(): void {
    const ctx = this.surface().nativeElement.getContext('2d');
    ctx?.fillRect(10, 10, 100, 60);
  }
}

Querying by component or directive type

You can query by a template reference variable (a string) or by a component/directive class. Querying by class returns the instance, letting you call its public methods and read its signals.

import { Component, viewChild, contentChild } from '@angular/core';
import { TabComponent } from './tab.component';

@Component({
  selector: 'app-panel',
  standalone: true,
  template: `<app-tab title="Overview" />`,
  imports: [TabComponent],
})
export class PanelComponent {
  firstTab = viewChild(TabComponent);

  activate(): void {
    this.firstTab()?.select(); // call a public method on the child instance
  }
}

ContentChild and projected content

contentChild() queries elements the parent passes through projection. The host component does not render these itself — it receives them via <ng-content>.

import { Component, contentChild, contentChildren } from '@angular/core';
import { TabComponent } from './tab.component';

@Component({
  selector: 'app-tab-group',
  standalone: true,
  template: `<div class="tabs"><ng-content /></div>`,
})
export class TabGroupComponent {
  // First projected <app-tab>.
  activeTab = contentChild(TabComponent);
  // All projected tabs as a reactive signal of a readonly array.
  allTabs = contentChildren(TabComponent);

  count = () => this.allTabs().length;
}
<app-tab-group>
  <app-tab title="Account" />
  <app-tab title="Billing" />
</app-tab-group>

The classic decorator API

Decorator queries still work and remain common in existing codebases. Their key constraint is timing: view queries resolve in ngAfterViewInit, and content queries in ngAfterContentInit. Reading them earlier yields undefined.

import {
  Component, ViewChild, ViewChildren, ElementRef, QueryList, AfterViewInit,
} from '@angular/core';
import { TabComponent } from './tab.component';

@Component({
  selector: 'app-legacy',
  standalone: true,
  template: `
    <input #name />
    <app-tab title="One" />
    <app-tab title="Two" />
  `,
  imports: [TabComponent],
})
export class LegacyComponent implements AfterViewInit {
  @ViewChild('name') nameRef!: ElementRef<HTMLInputElement>;
  @ViewChildren(TabComponent) tabs!: QueryList<TabComponent>;

  ngAfterViewInit(): void {
    this.nameRef.nativeElement.focus();
    console.log('Tab count:', this.tabs.length);
  }
}

Output:

Tab count: 2

The static option

@ViewChild accepts { static: true } for queries that resolve before change detection (an element not inside a @if/@for), making them available in ngOnInit. Signal queries make this flag unnecessary.

@ViewChild('header', { static: true }) header!: ElementRef;

Decorator vs signal queries

AspectDecorator (@ViewChild)Signal (viewChild())
Result typePlain propertySignal<T> (call to read)
AvailabilityLifecycle hook onlyReactive, read any time after render
ReactivityManualWorks in effect/computed
Required variantNot built inviewChild.required()
Multiple resultsQueryList<T>Signal<readonly T[]>

Best practices

  • Prefer signal queries (viewChild/contentChild) in new code — they are reactive, simpler, and eliminate lifecycle timing bugs.
  • Use .required() when an element is guaranteed present so the type excludes undefined and missing elements fail loudly.
  • Query by component/directive class to interact with instances; use template reference strings for plain DOM elements.
  • Treat queried DOM elements as a last resort — prefer data binding and signals over direct nativeElement manipulation.
  • With decorator queries, read view children only in ngAfterViewInit and content children only in ngAfterContentInit.
  • Reach for contentChild/contentChildren only for projected content; use view queries for elements your own template renders.
Last updated June 14, 2026
Was this helpful?