Angular @ViewChild for Accessing Child Components, Directives, and DOM Elements
@ViewChild is an Angular decorator used to query a component template and retrieve the first matching child component, directive, or native DOM element. It allows a parent component class to work with elements in its own view programmatically without using standard DOM queries or manually passing events.
This guide explains how to use @ViewChild from a parent component class to access a child component, directive, or DOM element. It also explains the static option, the read option, @ViewChildren with QueryList, and the signal-based view query APIs introduced with Angular 17.
Key Takeaways
@ViewChildretrieves the first matching child component, directive, or DOM element located in a component’s own template.- By default, the result can be accessed in
ngAfterViewInit. Usestatic: trueonly when an element is never contained in a structural directive and must already be available duringngOnInit. @ViewChildrenprovides aQueryListcontaining every matching element. Subscribe toQueryList.changeswhen you need to respond to elements being dynamically added or removed.- The
readoption changes what a query returns, making it possible to retrieve anElementRefor a particular directive instance from the matched element. - Signal-based
viewChild()andviewChildren()provide the newer approach introduced with Angular 17. They are generally preferable for new projects using Angular 17 or newer. - Avoid manipulating the DOM directly through
ElementRef.nativeElementwhen Angular bindings orRenderer2can accomplish the same task.
Prerequisites
Before working through this guide, make sure your development environment includes:
- Node.js 18.x or newer with npm installed
- Angular CLI installed globally:
npm install -g @angular/cli
- Basic familiarity with Angular components and TypeScript decorators
The examples in this guide were validated with @angular/core v17 and @angular/cli v17.
What Is @ViewChild and Why Is It Used?
Angular components normally communicate through @Input and @Output bindings. There are situations, however, where a parent component needs to invoke a method on a child component, retrieve a property from a directive attached to a template element, or obtain a direct reference to a native DOM node. @ViewChild is intended for these situations.
Without @ViewChild, similar access could require DOM queries such as document.querySelector. That approach bypasses Angular’s normal change-detection mechanisms and can interfere with server-side rendering. @ViewChild instead works with Angular’s view initialization process and supplies a typed reference, reducing the need for unsafe direct DOM access.
How Angular’s View Query System Works
Angular creates a component in two main phases. During the first phase, the component class is instantiated and ngOnInit runs, but child elements declared in the template have not yet been fully created. During the second phase, Angular initializes the view by creating child components, attaching directives, and inserting DOM nodes. ngAfterViewInit runs when this second phase is complete. This explains why @ViewChild results are normally accessible in ngAfterViewInit rather than ngOnInit.
With static: false, which is the default behavior, Angular waits until this second phase before resolving the query. With static: true, Angular resolves it synchronously after the initial change-detection run. This occurs before ngAfterViewInit, while the template has already been compiled. Practical examples of the static option are covered later.
Setting Up the Example Project
Generating the Angular Application
Create a new Angular workspace:
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)
…
Move into the newly created project directory:
cd viewchild-demo
Creating a Child Component for the Examples
Create a child component that can be referenced by the examples:
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 attached to an element in a template, the parent component does not automatically receive a direct reference to that directive instance. @ViewChild makes this possible by querying the directive class and returning a typed reference to the instance attached to the current view.
The next example creates a SharkDirective that reads an appShark attribute and adds the word "Shark" before the text contained by the host element. The parent component then uses @ViewChild to retrieve a property from the directive instance.
Generate the directive with @angular/cli:
ng generate directive shark –skip-tests
The command creates shark.directive.ts and registers the directive in app.module.ts:
app.module.ts
import { SharkDirective } from ‘./shark.directive’;
…
@NgModule({
declarations: [
AppComponent,
SharkDirective
],
…
})
Next, use ElementRef together with Renderer2 to modify the displayed text. Replace the contents of shark.directive.ts with this code:
shark.directive.ts
import {
Directive,
ElementRef,
Renderer2
} from ‘@angular/core’;
@Directive(
{ selector: ‘[appShark]’ } // selects elements containing the appShark attribute
)
export class SharkDirective {
creature = ‘Dolphin’; // property that can be read through @ViewChild
constructor(elem: ElementRef, renderer: Renderer2) {
let shark = renderer.createText(‘Shark ‘); // create the text node
renderer.appendChild(elem.nativeElement, shark); // attach it to the host element
}
}
Now add the appShark attribute to a span containing some text. Replace the contents of app.component.html with the following:
app.component.html
Fin!
When the application is opened in a browser, "Shark" appears before the existing element content:
Output
Shark Fin!
The creature property from SharkDirective can also be accessed and assigned to an extraCreature property. Replace 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) {
// this setter runs when Angular resolves the @ViewChild query
this.extraCreature = directive.creature;
};
ngAfterViewInit() {
// the @ViewChild result is guaranteed to be available at this point
console.log(this.extraCreature); // Dolphin
}
}
This example uses a setter to assign the extraCreature property. Access to the value is delayed until the AfterViewInit lifecycle hook because this is the point where child components and directives are available. A setter is used rather than a direct property declaration because it runs synchronously whenever Angular assigns a new query result, including situations where the directive instance changes during runtime.
The browser will continue to display "Shark Fin!". The console, however, will show:
Output
Dolphin
Using ViewChild with DOM Elements
To retrieve a native DOM element through @ViewChild, first identify it in the template with a template reference variable. A template reference variable is a local identifier beginning with #. Angular then uses this identifier as the query selector. Without such a reference, @ViewChild cannot determine which template element should be returned.
The following example assigns #someInput to an <input> element. The parent component then retrieves that element and changes its value programmatically:
app.component.html
<input #someInput placeholder=”Your favorite sea creature”>
The <input> can now be accessed through ViewChild and its value can be changed. Replace 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;
ngAfterViewInit() {
// nativeElement provides the underlying HTMLInputElement
this.someInput.nativeElement.value = ‘Whale!’;
}
}
Note: Working directly with nativeElement bypasses Angular’s security model and can cause problems with server-side rendering. For production DOM operations, inject Renderer2 and use its available methods. When only a property needs to be set, prefer Angular template bindings such as [value] or [style].
When ngAfterViewInit runs, the value of the <input> becomes:
Output
Whale!
Using ViewChild with Child Components
@ViewChild can provide a parent component with a typed reference to an instance of a child component. Through that reference, the parent can invoke public child methods or retrieve public child properties without using an event emitter or shared service. This approach is appropriate when communication goes directly from parent to child and the interaction is initiated programmatically instead of through a user-generated event.
This section uses the previously generated PupComponent. If that setup step was skipped, create it now:
ng generate component pup –flat –skip-tests
The command creates pup.component.ts, pup.component.css, and pup.component.html, then registers the component in app.module.ts:
app.module.ts
import { PupComponent } from ‘./pup.component’;
…
@NgModule({
declarations: [
AppComponent,
PupComponent
],
…
})
Next, add a whoAmI method to PupComponent that 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 {
}
}
Then include the child component in the application template. Replace app.component.html with:
app.component.html
pup works!
The parent component can now invoke whoAmI by obtaining the child instance with ViewChild. Replace app.component.ts with:
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 pointing to the child component instance
@ViewChild(PupComponent) pup!: PupComponent;
ngAfterViewInit() {
// invoke a public method directly on the child component
console.log(this.pup.whoAmI()); // I am a pup component!
}
}
When the application runs, the browser console displays:
Output
I am a pup component!
Understanding the static Option
static: true vs static: false
The static setting determines when Angular resolves a @ViewChild query in relation to change detection.
| Option | Resolves | Available in | Use when |
|---|---|---|---|
static: false (default) |
After the first change-detection cycle | ngAfterViewInit |
The element is located inside *ngIf, *ngFor, or another structural directive |
static: true |
Before the first change-detection cycle | ngOnInit |
The element is permanently present and is not wrapped by a structural directive |
Practical Example of the Difference
When static: false is used, which is the default, retrieve the result in ngAfterViewInit:
// app.component.ts
@ViewChild(‘myElement’) myElement!: ElementRef; // static: false is used by default
ngOnInit() {
console.log(this.myElement); // undefined: the query has not been resolved yet
}
ngAfterViewInit() {
console.log(this.myElement); // ElementRef: resolved after view initialization
}
When static: true is configured, the reference is already accessible in ngOnInit:
// app.component.ts
@ViewChild(‘myElement’, { static: true }) myElement!: ElementRef;
ngOnInit() {
console.log(this.myElement); // ElementRef: resolved before change detection
}
A query targeting an element inside a structural directive should not use static: true. Angular cannot resolve that query before change detection when the element might not yet be present in the DOM.
Using the read Option to Change the Return Type
The normal return type of a @ViewChild query depends on what Angular matches. If the selector identifies a component, Angular returns the component instance. If it identifies a directive, Angular returns the directive instance. The read option allows this default behavior to be overridden. Typical use cases include retrieving the underlying ElementRef from an element that hosts a component or selecting a particular directive when several directives are attached to the same element.
Reading an ElementRef from a Component Selector
Use this approach when the host DOM element is required instead of the component instance, such as when measuring dimensions or providing a raw DOM node to a third-party library.
// app.component.ts
// Without read, the query returns a PupComponent instance.
// With read: ElementRef, the query returns the component’s host DOM element.
@ViewChild(PupComponent, { read: ElementRef }) pupElement!: ElementRef;
ngAfterViewInit() {
console.log(this.pupElement.nativeElement.tagName); // APP-PUP
}
Reading a Directive from an Element
When several directives are attached to one element, the read option can specify which directive instance should be returned.
Add both #myRef and appShark to the same element in app.component.html:
app.component.html
Fin!
Then retrieve the SharkDirective instance attached 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 provides only the first matching result. When several elements use the same selector and references to all of them are needed, use @ViewChildren.
Declaring an @ViewChildren Query
Place two <app-pup> components in the template and query both at the same time:
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 {
// contains every PupComponent instance found in the template
@ViewChildren(PupComponent) pups!: QueryList;
ngAfterViewInit() {
console.log(this.pups.length); // 2
}
}
Iterating Over a QueryList
Use .forEach() to synchronously work with each matched instance once initialization has completed. For example, this can be used to invoke a setup method or retrieve an initial property value from each child:
ngAfterViewInit() {
this.pups.forEach((pup, index) => {
console.log(`Pup ${index}:`, pup.whoAmI());
});
}
Subscribing to QueryList Changes
QueryList includes a changes observable. It emits an updated QueryList whenever an item is added to or removed from the matched collection. One example is a child component entering or leaving the view because an *ngIf condition changes. The first render does not emit through changes; emissions occur only after subsequent updates.
ngAfterViewInit() {
// runs whenever a PupComponent enters or leaves the view
this.pups.changes.subscribe((list: QueryList) => {
console.log(‘Pup count:’, list.length);
});
}
If the component might be destroyed while this subscription remains active, keep a reference to the subscription and unsubscribe during ngOnDestroy to prevent a memory leak:
import { Subscription } from ‘rxjs’;
private pupSub!: Subscription;
ngAfterViewInit() {
this.pupSub = this.pups.changes.subscribe((list: QueryList) => {
console.log(‘Pup count:’, list.length);
});
}
ngOnDestroy() {
this.pupSub.unsubscribe();
}
Signal-Based View Queries in Angular 17+
Angular 17 added 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 applications | Limited | Yes |
| Available since | Angular 2 | Angular 17 |
@ViewChild can be used in zoneless applications, but when a queried reference changes it requires manual change-detection notification through ChangeDetectorRef.markForCheck(). Without Zone.js, automatic detection is not triggered. Signal-based viewChild() connects directly to Angular’s reactive graph and can update without this manual step.
Basic Signal Query Example
The next example uses a standalone component. The signal-based API is intended for the standalone component model and should not be declared in app.module.ts.
The example also uses afterNextRender, a lifecycle function introduced with Angular 17. It executes a callback once after the next DOM rendering cycle has completed. In situations without a class-based lifecycle, such as signal-based standalone components, it can take the place of ngAfterViewInit. Use afterNextRender when DOM data needs to be read or modified once after the initial render. It does not execute on the server during server-side rendering.
app.component.ts
import { Component, viewChild, ElementRef, afterNextRender } from ‘@angular/core’;
@Component({
selector: ‘app-root’,
template: “,
standalone: true
})
export class AppComponent {
// viewChild() provides a Signal
nameInput = viewChild(‘nameInput’);
constructor() {
afterNextRender(() => {
// retrieve the signal value after the view has rendered
console.log(this.nameInput()?.nativeElement.value);
});
}
}
When an element is guaranteed to exist, viewChild.required() can be used to eliminate the undefined union:
// Signal: throws an error when the query has no match
nameInput = viewChild.required(‘nameInput’);
When to Prefer Signal Queries
For new applications using Angular 17 or newer, consider viewChild() and viewChildren(), particularly when using zoneless change detection or standalone components. Applications based on Angular 16 or earlier should continue using @ViewChild and @ViewChildren.
The following example demonstrates how viewChildren() produces a signal containing multiple matched elements. In signal-based components, it can replace @ViewChildren. Before using this example, generate a ChildComponent with ng generate component child --flat --skip-tests --standalone:
app.component.ts
import { Component, viewChildren, afterNextRender } from ‘@angular/core’;
import { ChildComponent } from ‘./child.component’;
@Component({
selector: ‘app-root’,
template: `
`,
standalone: true,
imports: [ChildComponent]
})
export class AppComponent {
// viewChildren() provides Signal<readonlyarray>
children = viewChildren(ChildComponent);
constructor() {
afterNextRender(() => {
console.log(this.children().length); // 2
});
}
}
@ViewChild vs @ContentChild vs @ViewChildren
| Decorator / Function | Queries | Returns | Multiplicity | Available in |
|---|---|---|---|---|
@ViewChild |
The component’s own template | First matching item | Single | ngAfterViewInit or ngOnInit with static: true |
@ViewChildren |
The component’s own template | QueryList<T> |
Multiple | ngAfterViewInit |
@ContentChild |
Content projected through <ng-content> |
First matching item | Single | ngAfterContentInit |
viewChild() |
The component’s own template | Signal<T> |
Single | When read in Angular 17+ |
viewChildren() |
The component’s own template | Signal<ReadonlyArray<T>> |
Multiple | When read in Angular 17+ |
Use @ContentChild when creating reusable wrapper components that receive projected content. Use @ViewChild or viewChild() when the target is defined inside the component’s own template.
Common Mistakes and How to Avoid Them
Accessing @ViewChild Before ngAfterViewInit
Trying to use a @ViewChild property inside ngOnInit normally produces undefined because Angular has not initialized the component view yet.
Incorrect:
// app.component.ts
ngOnInit() {
// TypeError: properties of undefined cannot be read
console.log(this.someInput.nativeElement.value);
}
Correct:
// app.component.ts
ngAfterViewInit() {
console.log(this.someInput.nativeElement.value); // behaves as expected
}
Place the access logic inside ngAfterViewInit, where Angular guarantees that the view has been initialized and the query has been resolved.
Querying Elements Inside *ngIf or *ngFor
Using static: true for a query that points to an element controlled by a structural directive makes Angular attempt to resolve the query before that element is guaranteed to exist.
Incorrect:
// app.component.ts
@ViewChild(‘conditionalEl’, { static: true }) el!: ElementRef;
// static: true cannot resolve an element before *ngIf has potentially rendered it
Correct:
// app.component.ts
@ViewChild(‘conditionalEl’) el: ElementRef | undefined;
// static: false is the default and resolves after change detection
ngAfterViewInit() {
if (this.el) {
this.el.nativeElement.focus();
}
}
Using static: false together with an undefined check ensures that the reference is only accessed when the element actually exists in the DOM.
Overusing ElementRef When @Input or @Output Can Handle the Interaction
Using read: ElementRef to reach into a child component and change its presentation directly is unnecessary when the same result can be achieved through an @Input binding.
Incorrect:
// app.component.ts
// Retrieves the host through read: ElementRef and modifies its 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
// {{ message }}
<!– app.component.html –>
<app-pup [highlightColor]=”‘red’”></app-pup>
Sending the value through @Input maintains separation between the parent and child components while preserving Angular’s change-detection and security mechanisms.
Frequently Asked Questions
What Is the Difference Between @ViewChild and @ContentChild in Angular?
@ViewChild searches elements that are declared in the component’s own template. @ContentChild searches elements projected into a component through <ng-content>. Choose @ContentChild when creating reusable wrapper components that receive projected content from their parent.
Why Is My @ViewChild Property Undefined in ngOnInit?
By default, @ViewChild is resolved only after Angular initializes the view, which happens after ngOnInit. Move the logic that uses the reference into ngAfterViewInit. When access is specifically required in ngOnInit, static: true can be used, but only when the queried element is not located within a structural directive.
What Does the static Option Do in @ViewChild?
static: true instructs Angular to resolve the query before change detection, which makes the value accessible during ngOnInit. static: false, the default, resolves it after change detection so that it is available in ngAfterViewInit. Elements inside *ngIf or *ngFor should use static: false.
How Do I Access Multiple Child Elements with the Same Selector?
Use @ViewChildren rather than @ViewChild. It returns a QueryList containing all matching items. You can iterate through the collection with .forEach() or subscribe to .changes when you need to react to updates in the list.
Is It Safe to Manipulate the DOM Directly Using ElementRef from @ViewChild?
Manipulating the DOM directly through ElementRef.nativeElement bypasses Angular’s security model and can interfere with server-side rendering. For DOM operations, use Angular’s Renderer2 service, or prefer Angular bindings such as [style], [class], or @HostBinding whenever they can provide the required behavior.
What Is the Signal-Based Alternative to @ViewChild in Angular 17+?
Angular 17 introduced viewChild() as a reactive alternative to @ViewChild. It provides a signal that automatically reflects changes to the queried element and connects directly to Angular’s signal-based reactivity system. It is suitable for new applications targeting Angular 17 or newer.
Can @ViewChild Query an Element Inside an *ngIf Block?
Yes. The query must use static: false, which is the default. If the *ngIf condition is false and the element is therefore not rendered, the @ViewChild property is undefined. Check that the value is defined before using the queried reference.
What Is the read Option in @ViewChild Used For?
The read setting determines which type Angular retrieves from the matched element. For example, @ViewChild('myRef', { read: ElementRef }) returns an ElementRef even when the selector identifies a component. Similarly, @ViewChild('myRef', { read: MyDirective }) returns the instance of the specified directive attached to that element.
Conclusion
This guide demonstrated how @ViewChild can be used from a parent component class to access a child component, directive, or native DOM element. It also explained the static and read options, retrieving multiple elements through @ViewChildren and QueryList, and using the signal-based viewChild() API introduced with Angular 17.
With these techniques, typed references can be retrieved for elements within a component template, query resolution can be controlled in relation to Angular’s initialization lifecycle, and either decorator-based or signal-based query APIs can be selected according to the Angular version used by a project.


