ViewChild in Angular: Components, Directives, and DOM Elements

Jun 05, 2026 03:00 AM - 2 days ago 628

Introduction

@ViewChild is an Angular decorator that queries a component’s template and returns the first matching kid constituent instance, directive instance, or native DOM element. It gives a genitor constituent people nonstop programmatic access to its position children without relying connected vanilla DOM querying aliases manual event passing.

This tutorial covers really to usage @ViewChild to entree a kid component, a directive, and a DOM constituent from a genitor constituent class. It besides covers the static option, the publication option, @ViewChildren pinch QueryList, and signal-based position queries introduced successful Angular 17.

Key Takeaways

  • @ViewChild returns the first matching kid component, directive, aliases DOM element from a component’s ain template.
  • The consequence is disposable successful ngAfterViewInit by default. Use static: true only for elements that are ne'er wrong a structural directive and request to be disposable successful ngOnInit.
  • @ViewChildren returns a QueryList of each matching elements. Subscribe to QueryList.changes to respond to move additions and removals.
  • The publication action overrides the return type, letting you retrieve an ElementRef aliases a circumstantial directive lawsuit from a matched element.
  • Signal-based viewChild() and viewChildren() are the modern alternative introduced successful Angular 17. Prefer them successful caller projects targeting Angular 17 or later.
  • Avoid nonstop DOM manipulation via ElementRef.nativeElement wherever Angular bindings aliases Renderer2 tin execute the aforesaid result.

Prerequisites

Before pursuing this tutorial, guarantee your situation has:

  • Node.js 18.x aliases later and npm installed
  • Angular CLI installed globally:
  1. npm install -g @angular/cli
  • Familiarity pinch Angular components and TypeScript decorators

This tutorial was validated pinch @angular/core v17 and @angular/cli v17.

What Is @ViewChild and Why It Exists

Angular components pass done @Input and @Output bindings successful most cases. However, immoderate scenarios require a genitor constituent to telephone a method connected a child component, publication a spot from a directive applied to a template element, or straight reference a autochthonal DOM node. @ViewChild covers these cases.

Without @ViewChild, you would request to query the DOM using document.querySelector, which bypasses Angular’s alteration discovery and breaks server-side rendering. @ViewChild integrates pinch Angular’s view initialization rhythm and returns a typed reference, avoiding unsafe DOM access.

How Angular’s View Query System Works

Angular builds a constituent successful 2 phases. In the first shape it instantiates the constituent people and runs ngOnInit, but the kid elements defined in the template person not been created yet. In the 2nd shape Angular initializes the view, creating kid components, applying directives, and inserting DOM nodes. ngAfterViewInit fires astatine the extremity of this 2nd phase, which is why @ViewChild results are disposable location and not successful ngOnInit.

When static: mendacious (the default), Angular waits for this 2nd shape before resolving the query. When static: true, Angular resolves the query synchronously aft the first alteration discovery run, which happens before ngAfterViewInit but still aft the template is compiled. The static option is covered successful a dedicated conception beneath pinch applicable examples.

Setting Up the Example Project

Generating the Angular Application

Create a caller Angular workspace:

  1. ng caller viewchild-demo --no-standalone --routing=false --style=css

Output

CREATE viewchild-demo/src/app/app.component.ts (219 bytes) CREATE viewchild-demo/src/app/app.module.ts (314 bytes) CREATE viewchild-demo/src/app/app.component.html (23115 bytes) ...

Change into the task directory:

  1. cd viewchild-demo

Creating a Child Component for the Examples

Generate a kid constituent that the examples will query:

  1. ng make constituent pup --flat --skip-tests

Output

CREATE src/app/pup.component.css (0 bytes) CREATE src/app/pup.component.html (19 bytes) CREATE src/app/pup.component.ts (188 bytes) UPDATE src/app/app.module.ts (467 bytes)

Using ViewChild pinch Directives

When a directive is applied to a template element, the genitor constituent has no nonstop reference to that directive lawsuit by default. @ViewChild solves this by letting you query for the directive people and get backmost a typed reference to the lawsuit applied successful the existent view.

The pursuing illustration builds a SharkDirective that sounds an appShark attribute and prepends the connection "Shark" to the big element’s text. The parent constituent past uses @ViewChild to publication a spot disconnected that directive instance.

Use @angular/cli to make the directive:

  1. ng make directive shark --skip-tests

This bid creates a shark.directive.ts record and registers the directive successful app.module.ts:

app.module.ts

import { SharkDirective } from './shark.directive'; ... @NgModule({ declarations: [ AppComponent, SharkDirective ], ... })

Then, usage ElementRef and Renderer2 to rewrite the text. Replace the contents of shark.directive.ts pinch the following:

shark.directive.ts

import { Directive, ElementRef, Renderer2 } from '@angular/core'; @Directive( { selector: '[appShark]' } // matches immoderate constituent pinch the appShark attribute ) export class SharkDirective { animal = 'Dolphin'; // lawsuit adaptable accessible via @ViewChild constructor(elem: ElementRef, renderer: Renderer2) { let shark = renderer.createText('Shark '); // create a matter node renderer.appendChild(elem.nativeElement, shark); // append to the big element } }

Next, adhd an appShark property to a span containing matter successful the constituent template. Replace the contents of app.component.html pinch the following:

app.component.html

<span appShark>Fin!</span>

When viewing the exertion successful a browser, it will render the connection "Shark" earlier the contents of the element:

Output

Shark Fin!

Now, you tin besides entree the animal lawsuit adaptable of SharkDirective and group an extraCreature lawsuit adaptable pinch its value. Replace the contents of app.component.ts pinch the following:

app.component.ts

import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { SharkDirective } from './shark.directive'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements AfterViewInit { extraCreature!: string; @ViewChild(SharkDirective) set appShark(directive: SharkDirective) { // setter fires erstwhile Angular resolves the @ViewChild query this.extraCreature = directive.creature; }; ngAfterViewInit() { // @ViewChild is guaranteed to beryllium resolved here console.log(this.extraCreature); // Dolphin } }

This codification utilized a setter to group the extraCreature variable. Notice that it waits for the AfterViewInit lifecycle hook to entree the variable, arsenic this is erstwhile kid components and directives go available. The setter shape is utilized present alternatively of a nonstop spot declaration because it fires synchronously whenever Angular assigns the query result, including if the directive lawsuit is replaced astatine runtime.

When viewing the exertion successful a browser, you will still spot the "Shark Fin!" message. However, successful the console log, it will display:

Output

Dolphin

Using ViewChild pinch DOM Elements

To query a autochthonal DOM constituent pinch @ViewChild, you must first people it in the template pinch a template reference variable, a section sanction prefixed with #. Angular uses that sanction arsenic the selector. Without it, @ViewChild has no measurement to place which constituent to return from the template.

The pursuing illustration marks an <input> pinch #someInput, past sounds it from the genitor constituent people to group its worth programmatically:

app.component.html

<input #someInput placeholder="Your favourite oversea creature">

Now, you tin entree the <input> pinch ViewChild and group the value. Replace the contents of app.component.ts pinch the following:

app.component.ts

import { Component, ViewChild, AfterViewInit, ElementRef } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements AfterViewInit { @ViewChild('someInput') someInput!: ElementRef<HTMLInputElement>; ngAfterViewInit() { // nativeElement exposes the underlying HTMLInputElement this.someInput.nativeElement.value = 'Whale!'; } }

Note: Direct entree to nativeElement bypasses Angular’s information model and breaks server-side rendering. For accumulation DOM operations, inject Renderer2 and usage its methods. For elemental spot binding, for illustration Angular template bindings specified arsenic [value] aliases [style] instead.

When ngAfterViewInit fires the worth of the <input> will beryllium group to:

Output

Whale!

Using ViewChild pinch Child Components

@ViewChild gives the genitor constituent a typed reference to a child component instance. This lets you telephone nationalist methods connected the kid or read its nationalist properties straight from the genitor class, without needing an arena emitter aliases a shared service. Use this shape erstwhile the interaction is purely parent-to-child and is triggered programmatically alternatively than by a personification event.

This conception uses PupComponent, which was generated successful the Setting Up the Example Project section. If you skipped that section, make it now:

  1. ng make constituent pup --flat --skip-tests

The bid creates pup.component.ts, pup.component.css, and pup.component.html and registers the constituent successful app.module.ts:

app.module.ts

import { PupComponent } from './pup.component'; ... @NgModule({ declarations: [ AppComponent, PupComponent ], ... })

Then, adhd a whoAmI method to PupComponent which returns a message:

pup.component.ts

import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-pup', templateUrl: './pup.component.html', styleUrls: ['./pup.component.css'] }) export class PupComponent implements OnInit { constructor() { } whoAmI() { return 'I americium a pup component!'; } ngOnInit(): void { } }

Next, reference the kid constituent successful the app template. Replace the contents of app.component.html pinch the following:

app.component.html

<app-pup>pup works!</app-pup>

Now, you tin telephone the whoAmI method from wrong the genitor constituent people pinch ViewChild. Replace the contents of app.component.ts pinch the following:

app.component.ts

import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { PupComponent } from './pup.component'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements AfterViewInit { // typed reference to the kid constituent instance @ViewChild(PupComponent) pup!: PupComponent; ngAfterViewInit() { // telephone a method straight connected the kid constituent instance console.log(this.pup.whoAmI()); // I americium a pup component! } }

When viewing the exertion successful a browser, the console log will display:

Output

I americium a pup component!

Understanding the fixed Option

static: existent vs static: false

The fixed action controls erstwhile Angular resolves the @ViewChild query relative to alteration detection.

Option Resolves Available in Use when
static: mendacious (default) After first alteration detection ngAfterViewInit Element is wrong *ngIf, *ngFor, aliases immoderate structural directive
static: true Before first alteration detection ngOnInit Element is ever coming and ne'er wrapped successful a structural directive

Practical Example Showing the Difference

With static: mendacious (the default), entree the query consequence in ngAfterViewInit:

// app.component.ts @ViewChild('myElement') myElement!: ElementRef; // static: mendacious is the default ngOnInit() { console.log(this.myElement); // undefined: query not yet resolved } ngAfterViewInit() { console.log(this.myElement); // ElementRef: resolved aft position initialization }

With static: true, the consequence is disposable successful ngOnInit:

// app.component.ts @ViewChild('myElement', { static: true }) myElement!: ElementRef; ngOnInit() { console.log(this.myElement); // ElementRef: resolved earlier alteration detection }

Do not usage static: existent connected an constituent wrong a structural directive. Angular cannot resoluteness the query earlier alteration discovery if the constituent whitethorn not yet exist successful the DOM.

Using the publication Option to Change the Return Type

The default return type of a @ViewChild query is wished by what Angular finds astatine the matched element: a constituent lawsuit if the selector matches a component, a directive lawsuit if the selector matches a directive. The read option overrides this. The communal cases wherever you request it are: getting the raw ElementRef for a big constituent moreover erstwhile a constituent is applied to it, or selecting a circumstantial directive from an constituent that has much than 1 directive applied.

Reading an ElementRef from a Component Selector

Use this erstwhile you request the big DOM constituent of a constituent alternatively than the component lawsuit itself, for illustration to measurement its dimensions aliases walk it to a third-party room that expects a earthy DOM node.

// app.component.ts // Without read, this returns a PupComponent instance. // With read: ElementRef, it returns the big DOM constituent instead. @ViewChild(PupComponent, { read: ElementRef }) pupElement!: ElementRef; ngAfterViewInit() { console.log(this.pupElement.nativeElement.tagName); // APP-PUP }

Reading a Directive from an Element

If aggregate directives are applied to the aforesaid element, usage publication to specify which 1 to return:

Add #myRef and appShark to the aforesaid constituent successful app.component.html:

app.component.html

<span #myRef appShark>Fin!</span>

Then query the SharkDirective lawsuit applied to that element:

// app.component.ts @ViewChild('myRef', { read: SharkDirective }) sharkDir!: SharkDirective; ngAfterViewInit() { console.log(this.sharkDir.creature); // Dolphin }

Accessing Multiple Elements pinch @ViewChildren and QueryList

@ViewChild returns only the first matching element. Use @ViewChildren when multiple elements stock the aforesaid selector and you request references to each of them.

Declaring a @ViewChildren Query

Add 2 <app-pup> instances to the template and query them together:

app.component.html

<app-pup></app-pup> <app-pup></app-pup>

app.component.ts

import { Component, ViewChildren, AfterViewInit, QueryList } from '@angular/core'; import { PupComponent } from './pup.component'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements AfterViewInit { // returns each PupComponent instances successful the template @ViewChildren(PupComponent) pups!: QueryList<PupComponent>; ngAfterViewInit() { console.log(this.pups.length); // 2 } }

Iterating Over a QueryList

Use .forEach() to entree each matched lawsuit synchronously astatine the point of initialization, for illustration to telephone a setup method aliases publication an initial spot worth from each child:

ngAfterViewInit() { this.pups.forEach((pup, index) => { console.log(`Pup ${index}:`, pup.whoAmI()); }); }

Subscribing to QueryList Changes

QueryList exposes a changes observable that emits a caller QueryList whenever elements are added to aliases removed from the matched set, for example when an *ngIf information toggles a kid constituent successful aliases retired of the view. The first render does not trigger changes; it only fires connected subsequent updates.

ngAfterViewInit() { // fires whenever a PupComponent is added to aliases removed from the view this.pups.changes.subscribe((list: QueryList<PupComponent>) => { console.log('Pup count:', list.length); }); }

If the constituent tin beryllium destroyed while the subscription is active, store the subscription and unsubscribe successful ngOnDestroy to debar a representation leak:

import { Subscription } from 'rxjs'; private pupSub!: Subscription; ngAfterViewInit() { this.pupSub = this.pups.changes.subscribe((list: QueryList<PupComponent>) => { console.log('Pup count:', list.length); }); } ngOnDestroy() { this.pupSub.unsubscribe(); }

Signal-Based View Queries successful Angular 17+

Angular 17 introduced viewChild() and viewChildren() arsenic signal-based alternatives to @ViewChild and @ViewChildren.

viewChild() vs @ViewChild

Feature @ViewChild viewChild()
Return type Direct reference Signal<T | undefined>
Requires ngAfterViewInit Yes No
Works pinch zoneless apps Limited Yes
Available since Angular 2 Angular 17

@ViewChild useful successful zoneless applications but requires manual change detection notification via ChangeDetectorRef.markForCheck() erstwhile the queried reference updates, because location is nary Zone.js to trigger automatic detection. Signal-based viewChild() integrates straight with Angular’s reactive chart and updates without manual intervention.

Basic Signal Query Example

The pursuing illustration uses a standalone component. The signal-based API is designed for the standalone constituent exemplary and should not beryllium declared in app.module.ts.

The illustration uses afterNextRender, a lifecycle usability introduced in Angular 17 that runs a callback erstwhile aft the adjacent DOM render cycle completes. It replaces ngAfterViewInit successful contexts wherever location is no class-based lifecycle, specified arsenic signal-based standalone components. Use afterNextRender erstwhile you request to publication aliases constitute to the DOM erstwhile after the first render. It does not tally connected the server during server-side rendering.

app.component.ts

import { Component, viewChild, ElementRef, afterNextRender } from '@angular/core'; @Component({ selector: 'app-root', template: `<input #nameInput placeholder="Enter name">`, standalone: true }) export class AppComponent { // viewChild() returns Signal<ElementRef | undefined> nameInput = viewChild<ElementRef>('nameInput'); constructor() { afterNextRender(() => { // publication the awesome worth aft the position renders console.log(this.nameInput()?.nativeElement.value); }); } }

For an constituent that is ever present, usage viewChild.required() to remove the undefined union:

// Signal<ElementRef>: throws if the query finds nary match nameInput = viewChild.required<ElementRef>('nameInput');

When to Prefer Signal Queries

Use viewChild() and viewChildren() successful caller projects targeting Angular 17 or later, particularly erstwhile adopting zoneless alteration discovery aliases the standalone component model. For projects connected Angular 16 aliases earlier, usage @ViewChild and @ViewChildren.

For completeness, present is really viewChildren() returns a awesome complete multiple matched elements. This replaces @ViewChildren successful signal-based components. For this example, make a ChildComponent with ng make constituent kid --flat --skip-tests --standalone before running the code:

app.component.ts

import { Component, viewChildren, afterNextRender } from '@angular/core'; import { ChildComponent } from './child.component'; @Component({ selector: 'app-root', template: ` <app-child></app-child> <app-child></app-child> `, standalone: true, imports: [ChildComponent] }) export class AppComponent { // viewChildren() returns Signal<ReadonlyArray<ChildComponent>> children = viewChildren(ChildComponent); constructor() { afterNextRender(() => { console.log(this.children().length); // 2 }); } }

@ViewChild vs @ContentChild vs @ViewChildren

Decorator / Function Queries Returns Multiplicity Available in
@ViewChild Component’s ain template First match Single ngAfterViewInit (or ngOnInit pinch static: true)
@ViewChildren Component’s ain template QueryList<T> Multiple ngAfterViewInit
@ContentChild Projected contented via <ng-content> First match Single ngAfterContentInit
viewChild() Component’s ain template Signal<T> Single On publication (Angular 17+)
viewChildren() Component’s ain template Signal<ReadonlyArray<T>> Multiple On publication (Angular 17+)

Use @ContentChild erstwhile building reusable wrapper components that accept projected content. Use @ViewChild aliases viewChild() for elements successful the component’s ain template.

Common Mistakes and How to Avoid Them

Accessing @ViewChild Before ngAfterViewInit

Accessing a @ViewChild spot successful ngOnInit returns undefined because Angular has not yet initialized the view.

Incorrect:

// app.component.ts ngOnInit() { // TypeError: cannot publication properties of undefined console.log(this.someInput.nativeElement.value); }

Correct:

// app.component.ts ngAfterViewInit() { console.log(this.someInput.nativeElement.value); // useful arsenic expected }

Move entree logic to ngAfterViewInit, wherever Angular guarantees the view is initialized and the query is resolved.

Querying Elements Inside *ngIf aliases *ngFor

Setting static: existent connected a query targeting an constituent wrong a structural directive causes Angular to effort solution earlier the constituent whitethorn exist.

Incorrect:

// app.component.ts @ViewChild('conditionalEl', { static: true }) el!: ElementRef; // static: existent cannot resoluteness an constituent that *ngIf whitethorn not person rendered yet

Correct:

// app.component.ts @ViewChild('conditionalEl') el: ElementRef | undefined; // static: mendacious (default) resolves aft alteration detection ngAfterViewInit() { if (this.el) { this.el.nativeElement.focus(); } }

Using static: mendacious and guarding against undefined ensures the query only runs erstwhile the constituent is coming successful the DOM.

Overusing ElementRef for What @Input/@Output Can Handle

Reaching into a kid constituent via read: ElementRef to manipulate its appearance is unnecessary erstwhile an @Input binding tin execute the aforesaid result.

Incorrect:

// app.component.ts // Uses read: ElementRef to scope the big constituent and group a style directly. @ViewChild(PupComponent, { read: ElementRef }) pupEl!: ElementRef; ngAfterViewInit() { this.pupEl.nativeElement.style.color = 'red'; }

Correct:

// pup.component.ts import { Input } from '@angular/core'; export class PupComponent { @Input() highlightColor: string = ''; } // pup.component.html // <span [style.color]="highlightColor">{{ connection }}</span> <!-- app.component.html --> <app-pup [highlightColor]="'red'"></app-pup>

Passing information done @Input keeps the genitor and kid decoupled and preserves Angular’s alteration discovery and information model.

Frequently Asked Questions

What is the quality betwixt @ViewChild and @ContentChild successful Angular?

@ViewChild queries elements defined successful the component’s ain template. @ContentChild queries elements projected into the constituent via <ng-content>. Use @ContentChild erstwhile building reusable wrapper components that judge projected contented from a parent.

Why is my @ViewChild spot undefined successful ngOnInit?

By default, @ViewChild resolves aft the position is initialized, which occurs after ngOnInit. Move your entree logic to ngAfterViewInit. If you need access successful ngOnInit, group static: true, but only erstwhile the queried element is not wrong a structural directive.

What does the fixed action do successful @ViewChild?

static: existent tells Angular to resoluteness the query earlier alteration detection runs, making the consequence disposable successful ngOnInit. static: mendacious (the default) resolves the query aft alteration detection, making it disposable in ngAfterViewInit. Use static: mendacious for elements wrong *ngIf or *ngFor.

How do I entree aggregate kid elements pinch the aforesaid selector?

Use @ViewChildren alternatively of @ViewChild. It returns a QueryList containing each matching elements. Iterate pinch .forEach() aliases subscribe to .changes to respond erstwhile the database updates.

Is it safe to manipulate the DOM straight utilizing ElementRef from @ViewChild?

Direct DOM manipulation via ElementRef.nativeElement bypasses Angular’s security exemplary and breaks server-side rendering. Use Angular’s Renderer2 service for DOM operations, aliases for illustration Angular bindings specified arsenic [style], [class], aliases @HostBinding wherever possible.

What is the signal-based replacement to @ViewChild successful Angular 17+?

Angular 17 introduced viewChild() arsenic a reactive alternative. It returns a signal that updates automatically erstwhile the queried constituent changes, integrating with Angular’s signal-based reactivity model. Use it successful caller projects targeting Angular 17 aliases later.

Can @ViewChild query an constituent wrong an *ngIf block?

Yes, but only pinch static: mendacious (the default). When the *ngIf condition is mendacious and the constituent is not rendered, the @ViewChild spot is undefined. Always cheque for undefined earlier accessing the queried reference.

What is the publication action successful @ViewChild utilized for?

The publication action tells Angular what type to inject for the matched element. For example, @ViewChild('myRef', { read: ElementRef }) returns an ElementRef moreover if the selector matches a component. @ViewChild('myRef', { read: MyDirective }) returns the directive instance applied to that element.

Conclusion

This tutorial covered really to usage @ViewChild to entree a kid component, a directive, and a autochthonal DOM constituent from a genitor constituent class. It also covered the fixed and publication options, querying aggregate elements with @ViewChildren and QueryList, and the signal-based viewChild() API available successful Angular 17.

You tin now query typed references to immoderate constituent successful a component’s template, control erstwhile queries resoluteness comparative to Angular’s initialization lifecycle, and take betwixt the decorator-based and signal-based query APIs based on your project’s Angular version.

For related topics, spot the Angular lifecycle hooks tutorial and the @ViewChildren API reference. For much Angular content, sojourn the DigitalOcean Angular taxable page.

Creative CommonsThis activity is licensed nether a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.

More