惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
U
Unit 42
MyScale Blog
MyScale Blog
B
Blog
阮一峰的网络日志
阮一峰的网络日志
量子位
IT之家
IT之家
The GitHub Blog
The GitHub Blog
F
Fortinet All Blogs
Recent Announcements
Recent Announcements
V
Visual Studio Blog
G
Google Developers Blog
Last Week in AI
Last Week in AI
雷峰网
雷峰网
博客园 - 聂微东
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
博客园 - 司徒正美
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
月光博客
月光博客
aimingoo的专栏
aimingoo的专栏

DigitalOcean Community Tutorials

It's Time to Break Up with Your Cloud: Why AI Teams are Switching We Built a Private-Document AI App to Test Platform Security. Here Is What We Could Actually Verify. PostgreSQL Explained: A Complete Beginner-to-Advanced Guide How To Install and Configure Postfix on Ubuntu How To Build a Web Application Using Flask in Python 3 Build AI Reading List with DigitalOcean Functions and Mistral How To Concatenate Strings in Python How to Allow MySQL Remote Access Securely How To Install and Use Docker on Rocky Linux How To Build a Multi-Agent AI System with Docker Agent DSPy Use Cases: Build Optimized LLM Pipelines How To Submit AJAX Forms with jQuery Build an AI-Powered GPU Fleet Optimizer with the DigitalOcean AI Platform ADK Monitor GPU Utilization in Real Time: A Complete Guide Reduce File Size of Images in Linux - CLI and GUI methods Reduce PDF File Size in Linux: Tools and Methods How To Set Up a Private Docker Registry on Ubuntu How To Troubleshoot Terraform: Errors and Fixes How to Use Go Modules Python Multiprocessing Example: Process, Pool & Queue Convert Class Components to Functional Components with React Hooks How To Install and Configure Ansible on Ubuntu LLM Tokenizers Simplified: BPE, SentencePiece, and More How To Monitor System Authentication Logs on Ubuntu How to Use Traceroute and MTR to Diagnose Network Issues How to Deploy Postgres to Kubernetes Cluster Importing Packages in Go: A Complete Guide Create RAID Arrays with mdadm on Ubuntu How To Make an HTTP Server in Go How To Set Up Time Synchronization on Ubuntu
ViewChild in Angular: Components, Directives, and DOM Ele...
Alligator · 2026-06-05 · via DigitalOcean Community Tutorials

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.

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.