By Alligator, Bradley Kouchi and Vinayak Baranwal

@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.
@ViewChild returns the first matching child component, directive, or DOM
element from a component’s own template.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.read option overrides the return type, letting you retrieve an
ElementRef or a specific directive instance from a matched element.viewChild() and viewChildren() are the modern alternative
introduced in Angular 17. Prefer them in new projects targeting Angular 17
or later.ElementRef.nativeElement where Angular
bindings or Renderer2 can achieve the same result.Before following this tutorial, ensure your environment has:
- npm install -g @angular/cli
This tutorial was validated with @angular/core v17 and @angular/cli v17.
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.
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.
Create a new Angular workspace:
- ng new viewchild-demo --no-standalone --routing=false --style=css
OutputCREATE 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:
- cd viewchild-demo
Generate a child component that the examples will query:
- ng generate component pup --flat --skip-tests
OutputCREATE 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)
ViewChild with DirectivesWhen 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:
- ng generate directive shark --skip-tests
This command creates a shark.directive.ts file and registers the directive in 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:
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:
<span appShark>Fin!</span>
When viewing the application in a browser, it will render the word "Shark" before the contents of the element:
OutputShark 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:
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:
OutputDolphin
ViewChild with DOM ElementsTo 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:
<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:
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:
OutputWhale!
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:
- 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:
import { PupComponent } from './pup.component';
...
@NgModule({
declarations: [
AppComponent,
PupComponent
],
...
})
Then, add a whoAmI method to PupComponent which returns a message:
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-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:
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:
OutputI am a pup component!
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 |
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.
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.
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
}
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:
<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
}
@ViewChild returns only the first matching element. Use @ViewChildren when
multiple elements share the same selector and you need references to all of
them.
Add two <app-pup> instances to the template and query them together:
<app-pup></app-pup>
<app-pup></app-pup>
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
}
}
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());
});
}
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();
}
Angular 17 introduced viewChild() and viewChildren() as signal-based
alternatives to @ViewChild and @ViewChildren.
| 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.
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.
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');
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:
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
});
}
}
| 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.
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.
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.
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.
@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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Former Technical Editor at DigitalOcean. Expertise in areas including Vue.js, CSS, React, and more.
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.
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!
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.