Report this

What is the reason for this report?

ViewChild in Angular: Components, Directives, and DOM Elements

Updated on June 4, 2026
English
ViewChild in Angular: Components, Directives, and DOM Elements

Introduction

@ViewChild is an Angular decorator that queries a component’s template and returns the first matching child component instance, directive instance, or native DOM element. It gives a parent component class direct programmatic access to its view children without relying on vanilla DOM querying or manual event passing.

This tutorial covers how to use @ViewChild to access a child component, a directive, and a DOM element from a parent component class. It also covers the static option, the read option, @ViewChildren with QueryList, and signal-based view queries introduced in Angular 17.

Key Takeaways

  • @ViewChild returns the first matching child component, directive, or DOM element from a component’s own template.
  • The result is available in ngAfterViewInit by default. Use static: true only for elements that are never inside a structural directive and need to be available in ngOnInit.
  • @ViewChildren returns a QueryList of all matching elements. Subscribe to QueryList.changes to react to dynamic additions and removals.
  • The read option overrides the return type, letting you retrieve an ElementRef or a specific directive instance from a matched element.
  • Signal-based viewChild() and viewChildren() are the modern alternative introduced in Angular 17. Prefer them in new projects targeting Angular 17 or later.
  • Avoid direct DOM manipulation via ElementRef.nativeElement where Angular bindings or Renderer2 can achieve the same result.

Prerequisites

Before following this tutorial, ensure your environment has:

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

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

What Is @ViewChild and Why It Exists

Angular components communicate through @Input and @Output bindings in most cases. However, some scenarios require a parent component to call a method on a child component, read a property from a directive applied to a template element, or directly reference a native DOM node. @ViewChild covers these cases.

Without @ViewChild, you would need to query the DOM using document.querySelector, which bypasses Angular’s change detection and breaks server-side rendering. @ViewChild integrates with Angular’s view initialization cycle and returns a typed reference, avoiding unsafe DOM access.

How Angular’s View Query System Works

Angular builds a component in two phases. In the first phase it instantiates the component class and runs ngOnInit, but the child elements defined in the template have not been created yet. In the second phase Angular initializes the view, creating child components, applying directives, and inserting DOM nodes. ngAfterViewInit fires at the end of this second phase, which is why @ViewChild results are available there and not in ngOnInit.

When static: false (the default), Angular waits for this second phase before resolving the query. When static: true, Angular resolves the query synchronously after the first change detection run, which happens before ngAfterViewInit but still after the template is compiled. The static option is covered in a dedicated section below with practical examples.

Setting Up the Example Project

Generating the Angular Application

Create a new Angular workspace:

  1. ng new 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 project directory:

  1. cd viewchild-demo

Creating a Child Component for the Examples

Generate a child component that the examples will query:

  1. ng generate component 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 with Directives

When a directive is applied to a template element, the parent component has no direct reference to that directive instance by default. @ViewChild solves this by letting you query for the directive class and get back a typed reference to the instance applied in the current view.

The following example builds a SharkDirective that reads an appShark attribute and prepends the word "Shark" to the host element’s text. The parent component then uses @ViewChild to read a property off that directive instance.

Use @angular/cli to generate the directive:

  1. ng generate directive shark --skip-tests

This command creates a shark.directive.ts file and registers the directive in app.module.ts:

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

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

shark.directive.ts
import {
  Directive,
  ElementRef,
  Renderer2
} from '@angular/core';

@Directive(
  { selector: '[appShark]' }  // matches any element with the appShark attribute
)
export class SharkDirective {
  creature = 'Dolphin';  // instance variable accessible via @ViewChild

  constructor(elem: ElementRef, renderer: Renderer2) {
    let shark = renderer.createText('Shark ');  // create a text node
    renderer.appendChild(elem.nativeElement, shark);  // append to the host element
  }
}

Next, add an appShark attribute to a span containing text in the component template. Replace the contents of app.component.html with the following:

app.component.html
<span appShark>Fin!</span>

When viewing the application in a browser, it will render the word "Shark" before the contents of the element:

Output
Shark Fin!

Now, you can also access the creature instance variable of SharkDirective and set an extraCreature instance variable with its value. Replace the contents of app.component.ts with 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 when Angular resolves the @ViewChild query
    this.extraCreature = directive.creature;
  };

  ngAfterViewInit() {
    // @ViewChild is guaranteed to be resolved here
    console.log(this.extraCreature); // Dolphin
  }
}

This code used a setter to set the extraCreature variable. Notice that it waits for the AfterViewInit lifecycle hook to access the variable, as this is when child components and directives become available. The setter pattern is used here instead of a direct property declaration because it fires synchronously whenever Angular assigns the query result, including if the directive instance is replaced at runtime.

When viewing the application in a browser, you will still see the "Shark Fin!" message. However, in the console log, it will display:

Output
Dolphin

Using ViewChild with DOM Elements

To query a native DOM element with @ViewChild, you must first mark it in the template with a template reference variable, a local name prefixed with #. Angular uses that name as the selector. Without it, @ViewChild has no way to identify which element to return from the template.

The following example marks an <input> with #someInput, then reads it from the parent component class to set its value programmatically:

app.component.html
<input #someInput placeholder="Your favorite sea creature">

Now, you can access the <input> with ViewChild and set the value. Replace the contents of app.component.ts with 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 access to nativeElement bypasses Angular’s security model and breaks server-side rendering. For production DOM operations, inject Renderer2 and use its methods. For simple property binding, prefer Angular template bindings such as [value] or [style] instead.

When ngAfterViewInit fires the value of the <input> will be set to:

Output
Whale!

Using ViewChild with Child Components

@ViewChild gives the parent component a typed reference to a child component instance. This lets you call public methods on the child or read its public properties directly from the parent class, without needing an event emitter or a shared service. Use this pattern when the interaction is purely parent-to-child and is triggered programmatically rather than by a user event.

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

  1. ng generate component pup --flat --skip-tests

The command creates pup.component.ts, pup.component.css, and pup.component.html and registers the component in app.module.ts:

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

Then, add 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 am a pup component!';
  }

  ngOnInit(): void {
  }

}

Next, reference the child component in the app template. Replace the contents of app.component.html with the following:

app.component.html
<app-pup>pup works!</app-pup>

Now, you can call the whoAmI method from within the parent component class with ViewChild. Replace the contents of app.component.ts with 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 child component instance
  @ViewChild(PupComponent) pup!: PupComponent;

  ngAfterViewInit() {
    // call a method directly on the child component instance
    console.log(this.pup.whoAmI()); // I am a pup component!
  }
}

When viewing the application in a browser, the console log will display:

Output
I am a pup component!

Understanding the static Option

static: true vs static: false

The static option controls when Angular resolves the @ViewChild query relative to change detection.

Option Resolves Available in Use when
static: false (default) After first change detection ngAfterViewInit Element is inside *ngIf, *ngFor, or any structural directive
static: true Before first change detection ngOnInit Element is always present and never wrapped in a structural directive

Practical Example Showing the Difference

With static: false (the default), access the query result in ngAfterViewInit:

// app.component.ts
@ViewChild('myElement') myElement!: ElementRef;  // static: false is the default

ngOnInit() {
  console.log(this.myElement); // undefined: query not yet resolved
}

ngAfterViewInit() {
  console.log(this.myElement); // ElementRef: resolved after view initialization
}

With static: true, the result is available in ngOnInit:

// app.component.ts
@ViewChild('myElement', { static: true }) myElement!: ElementRef;

ngOnInit() {
  console.log(this.myElement); // ElementRef: resolved before change detection
}

Do not use static: true on an element inside a structural directive. Angular cannot resolve the query before change detection if the element may not yet exist in the DOM.

Using the read Option to Change the Return Type

The default return type of a @ViewChild query is determined by what Angular finds at the matched element: a component instance if the selector matches a component, a directive instance if the selector matches a directive. The read option overrides this. The common cases where you need it are: getting the raw ElementRef for a host element even when a component is applied to it, or selecting a specific directive from an element that has more than one directive applied.

Reading an ElementRef from a Component Selector

Use this when you need the host DOM element of a component rather than the component instance itself, for example to measure its dimensions or pass it to a third-party library that expects a raw DOM node.

// app.component.ts
// Without read, this returns a PupComponent instance.
// With read: ElementRef, it returns the host DOM element instead.
@ViewChild(PupComponent, { read: ElementRef }) pupElement!: ElementRef;

ngAfterViewInit() {
  console.log(this.pupElement.nativeElement.tagName); // APP-PUP
}

Reading a Directive from an Element

If multiple directives are applied to the same element, use read to specify which one to return:

Add #myRef and appShark to the same element in app.component.html:

app.component.html
<span #myRef appShark>Fin!</span>

Then query the SharkDirective instance applied to that element:

// app.component.ts
@ViewChild('myRef', { read: SharkDirective }) sharkDir!: SharkDirective;

ngAfterViewInit() {
  console.log(this.sharkDir.creature); // Dolphin
}

Accessing Multiple Elements with @ViewChildren and QueryList

@ViewChild returns only the first matching element. Use @ViewChildren when multiple elements share the same selector and you need references to all of them.

Declaring a @ViewChildren Query

Add two <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 all PupComponent instances in the template
  @ViewChildren(PupComponent) pups!: QueryList<PupComponent>;

  ngAfterViewInit() {
    console.log(this.pups.length); // 2
  }
}

Iterating Over a QueryList

Use .forEach() to access each matched instance synchronously at the point of initialization, for example to call a setup method or read an initial property value 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 new QueryList whenever elements are added to or removed from the matched set, for example when an *ngIf condition toggles a child component in or out of the view. The initial render does not trigger changes; it only fires on subsequent updates.

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

If the component can be destroyed while the subscription is active, store the subscription and unsubscribe in ngOnDestroy to avoid a memory 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 in Angular 17+

Angular 17 introduced viewChild() and viewChildren() as 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 with zoneless apps Limited Yes
Available since Angular 2 Angular 17

@ViewChild works in zoneless applications but requires manual change detection notification via ChangeDetectorRef.markForCheck() when the queried reference updates, because there is no Zone.js to trigger automatic detection. Signal-based viewChild() integrates directly with Angular’s reactive graph and updates without manual intervention.

Basic Signal Query Example

The following example uses a standalone component. The signal-based API is designed for the standalone component model and should not be declared in app.module.ts.

The example uses afterNextRender, a lifecycle function introduced in Angular 17 that runs a callback once after the next DOM render cycle completes. It replaces ngAfterViewInit in contexts where there is no class-based lifecycle, such as signal-based standalone components. Use afterNextRender when you need to read or write to the DOM once after the initial render. It does not run on 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(() => {
      // read the signal value after the view renders
      console.log(this.nameInput()?.nativeElement.value);
    });
  }
}

For an element that is always present, use viewChild.required() to remove the undefined union:

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

When to Prefer Signal Queries

Use viewChild() and viewChildren() in new projects targeting Angular 17 or later, especially when adopting zoneless change detection or the standalone component model. For projects on Angular 16 or earlier, use @ViewChild and @ViewChildren.

For completeness, here is how viewChildren() returns a signal over multiple matched elements. This replaces @ViewChildren in signal-based components. For this example, generate a ChildComponent with ng generate component child --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 own template First match Single ngAfterViewInit (or ngOnInit with static: true)
@ViewChildren Component’s own template QueryList<T> Multiple ngAfterViewInit
@ContentChild Projected content via <ng-content> First match Single ngAfterContentInit
viewChild() Component’s own template Signal<T> Single On read (Angular 17+)
viewChildren() Component’s own template Signal<ReadonlyArray<T>> Multiple On read (Angular 17+)

Use @ContentChild when building reusable wrapper components that accept projected content. Use @ViewChild or viewChild() for elements in the component’s own template.

Common Mistakes and How to Avoid Them

Accessing @ViewChild Before ngAfterViewInit

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

Incorrect:

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

Correct:

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

Move access logic to ngAfterViewInit, where Angular guarantees the view is initialized and the query is resolved.

Querying Elements Inside *ngIf or *ngFor

Setting static: true on a query targeting an element inside a structural directive causes Angular to attempt resolution before the element may exist.

Incorrect:

// app.component.ts
@ViewChild('conditionalEl', { static: true }) el!: ElementRef;
// static: true cannot resolve an element that *ngIf may not have rendered yet

Correct:

// app.component.ts
@ViewChild('conditionalEl') el: ElementRef | undefined;
// static: false (default) resolves after change detection

ngAfterViewInit() {
  if (this.el) {
    this.el.nativeElement.focus();
  }
}

Using static: false and guarding against undefined ensures the query only runs when the element is present in the DOM.

Overusing ElementRef for What @Input/@Output Can Handle

Reaching into a child component via read: ElementRef to manipulate its appearance is unnecessary when an @Input binding can achieve the same result.

Incorrect:

// app.component.ts
// Uses read: ElementRef to reach the host element and set 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">{{ message }}</span>

<!-- app.component.html -->
<app-pup [highlightColor]="'red'"></app-pup>

Passing data through @Input keeps the parent and child decoupled and preserves Angular’s change detection and security model.

Frequently Asked Questions

What is the difference between @ViewChild and @ContentChild in Angular?

@ViewChild queries elements defined in the component’s own template. @ContentChild queries elements projected into the component via <ng-content>. Use @ContentChild when building reusable wrapper components that accept projected content from a parent.

Why is my @ViewChild property undefined in ngOnInit?

By default, @ViewChild resolves after the view is initialized, which occurs after ngOnInit. Move your access logic to ngAfterViewInit. If you need access in ngOnInit, set static: true, but only when the queried element is not inside a structural directive.

What does the static option do in @ViewChild?

static: true tells Angular to resolve the query before change detection runs, making the result available in ngOnInit. static: false (the default) resolves the query after change detection, making it available in ngAfterViewInit. Use static: false for elements inside *ngIf or *ngFor.

How do I access multiple child elements with the same selector?

Use @ViewChildren instead of @ViewChild. It returns a QueryList containing all matching elements. Iterate with .forEach() or subscribe to .changes to react when the list updates.

Is it safe to manipulate the DOM directly using ElementRef from @ViewChild?

Direct DOM manipulation via ElementRef.nativeElement bypasses Angular’s security model and breaks server-side rendering. Use Angular’s Renderer2 service for DOM operations, or prefer Angular bindings such as [style], [class], or @HostBinding wherever possible.

What is the signal-based alternative to @ViewChild in Angular 17+?

Angular 17 introduced viewChild() as a reactive alternative. It returns a signal that updates automatically when the queried element changes, integrating with Angular’s signal-based reactivity model. Use it in new projects targeting Angular 17 or later.

Can @ViewChild query an element inside an *ngIf block?

Yes, but only with static: false (the default). When the *ngIf condition is false and the element is not rendered, the @ViewChild property is undefined. Always check for undefined before accessing the queried reference.

What is the read option in @ViewChild used for?

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

Conclusion

This tutorial covered how to use @ViewChild to access a child component, a directive, and a native DOM element from a parent component class. It also covered the static and read options, querying multiple elements with @ViewChildren and QueryList, and the signal-based viewChild() API available in Angular 17.

You can now query typed references to any element in a component’s template, control when queries resolve relative to Angular’s initialization lifecycle, and choose between the decorator-based and signal-based query APIs based on your project’s Angular version.

For related topics, see the Angular lifecycle hooks tutorial and the @ViewChildren API reference. For more Angular content, visit the DigitalOcean Angular topic page.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Alligator
Alligator
Author
See author profile

Alligator.io is a developer-focused resource that offers tutorials and insights on a wide range of modern front-end technologies, including Angular 2+, Vue.js, React, TypeScript, Ionic, and JavaScript.

Bradley Kouchi
Bradley Kouchi
Editor
See author profile

Former Technical Editor at DigitalOcean. Expertise in areas including Vue.js, CSS, React, and more.

Vinayak Baranwal
Vinayak Baranwal
Editor
Technical Writer II
See author profile

Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.

Category:
Tags:

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Start building today

From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.

Dark mode is coming soon.