Back to articles

Angular @ViewChild

10 min
Front-endAngular

Angular @ViewChild

@ViewChild gives you a direct reference to a DOM element or child component from within your TypeScript code.

It's the go-to solution when you need to manipulate the DOM directly or call a method on a child component.


Accessing a DOM Element

Declare a template variable with # in the template, then use @ViewChild to grab a reference to it in TypeScript.

TypeScript
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  standalone: true,
  selector: 'app-demo',
  template: `
    <input #nameInput placeholder="Enter name" />
    <button (click)="focusInput()">Focus input</button>
  `,
})
export class DemoComponent implements AfterViewInit {
  @ViewChild('nameInput') nameInput!: ElementRef<HTMLInputElement>;

  ngAfterViewInit() {
    console.log(this.nameInput.nativeElement);
  }

  focusInput() {
    this.nameInput.nativeElement.focus();
  }
}

@ViewChild('nameInput') takes the template variable name as an argument and returns an ElementRef. Access the underlying DOM node through ElementRef.nativeElement.


Accessing a Child Component

@ViewChild also works with child components, giving you access to their properties and methods directly.

Child component

TypeScript
import { Component } from '@angular/core';

@Component({
  standalone: true,
  selector: 'app-timer',
  template: `<p>{{ seconds }}s</p>`,
})
export class TimerComponent {
  seconds = 0;

  start() {
    setInterval(() => {
      this.seconds++;
    }, 1000);
  }

  reset() {
    this.seconds = 0;
  }
}

Parent component

TypeScript
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { TimerComponent } from './timer.component';

@Component({
  standalone: true,
  imports: [TimerComponent],
  selector: 'app-parent',
  template: `
    <app-timer />
    <button (click)="startTimer()">Start</button>
    <button (click)="resetTimer()">Reset</button>
  `,
})
export class ParentComponent implements AfterViewInit {
  @ViewChild(TimerComponent) timer!: TimerComponent;

  ngAfterViewInit() {
    console.log(this.timer); // TimerComponent instance
  }

  startTimer() {
    this.timer.start();
  }

  resetTimer() {
    this.timer.reset();
  }
}

Pass the class name instead of a string to get a typed reference to the component instance.


@ViewChildren

When the template has multiple elements or components of the same type, use @ViewChildren to get all of them as a QueryList:

TypeScript
import { Component, ViewChildren, QueryList, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  standalone: true,
  selector: 'app-demo',
  template: `
    <input #item placeholder="Item 1" />
    <input #item placeholder="Item 2" />
    <input #item placeholder="Item 3" />
    <button (click)="focusAll()">Focus all</button>
  `,
})
export class DemoComponent implements AfterViewInit {
  @ViewChildren('item') items!: QueryList<ElementRef<HTMLInputElement>>;

  ngAfterViewInit() {
    console.log(this.items.length); // 3
  }

  focusAll() {
    this.items.forEach(item => item.nativeElement.focus());
  }
}

QueryList supports forEach, map, filter, and more. You can also subscribe to changes to react when the list updates:

TypeScript
ngAfterViewInit() {
  this.items.changes.subscribe(() => {
    console.log('list changed');
  });
}

Things to Know

Only available after ngAfterViewInit

@ViewChild references aren't populated until after the view has been initialized. Accessing them in ngOnInit returns undefined:

TypeScript
ngOnInit() {
  console.log(this.nameInput); // undefined
}

ngAfterViewInit() {
  console.log(this.nameInput); // ElementRef — ready to use
}

The static Option

@ViewChild has a static option that defaults to false:

  • static: false (default) — resolved after change detection; available in ngAfterViewInit
  • static: true — resolved before change detection; available in ngOnInit, but only works for elements that aren't inside *ngIf or *ngFor
TypeScript
@ViewChild('nameInput', { static: true }) nameInput!: ElementRef;

ngOnInit() {
  console.log(this.nameInput); // works because static: true
}

Don't Overuse It

Direct DOM manipulation bypasses Angular's change detection and can lead to hard-to-track bugs. Reach for data binding first — use @ViewChild only when you genuinely need direct DOM access.


viewChild() (Angular 17+)

Angular 17 introduced viewChild(), a Signal-based alternative to @ViewChild:

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

@Component({
  standalone: true,
  selector: 'app-demo',
  template: `<input #nameInput placeholder="Name" />`,
})
export class DemoComponent {
  nameInput = viewChild<ElementRef>('nameInput');
  // required version:
  nameInputRequired = viewChild.required<ElementRef>('nameInput');
}

viewChild() returns a Signal, so you read the value by calling it with ():

TypeScript
ngAfterViewInit() {
  console.log(this.nameInput()?.nativeElement);
  console.log(this.nameInputRequired().nativeElement); // no ? needed
}

@ViewChild vs viewChild()

@ViewChildviewChild()
StyleDecoratorFunction
Return typeDirect valueSignal
Reading valueDirect property accessCall with ()
Required! assertionviewChild.required()
Angular versionAll versionsv17+

Summary

  • @ViewChild — gets a reference to a single DOM element or child component
  • @ViewChildren — gets a QueryList of all matching elements or components
  • References aren't available until ngAfterViewInit
  • Angular 17+ offers viewChild() as a Signal-based alternative — the recommended approach for new projects