Skip to content

Angular ViewChild and ContentChild

Sometimes binding alone isn't enough, you need direct programmatic access to a child element, component, or directive instance. This lesson covers @ViewChild and @ContentChild and their plural forms.

What Are ViewChild and ContentChild?

@ViewChild gives a component direct access to an element, directive, or child component defined in its own template. @ContentChild gives access to content that was projected into the component from its parent via <ng-content>.

Both are available once the relevant part of the component lifecycle finishes: @ViewChild after ngAfterViewInit, and @ContentChild after ngAfterContentInit.

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-search-box',
  standalone: true,
  template: '<input #searchInput type="text" />',
})
export class SearchBoxComponent implements AfterViewInit {
  @ViewChild('searchInput') inputRef!: ElementRef<HTMLInputElement>;

  ngAfterViewInit() {
    this.inputRef.nativeElement.focus();
  }
}

@ViewChild('searchInput') matches the template reference variable #searchInput and gives access to the native input element to auto-focus it.

ViewChild and ContentChild Syntax

@ViewChild('templateRefVar') ref!: ElementRef;
@ViewChild(ChildComponent) child!: ChildComponent;
@ViewChildren(ChildComponent) children!: QueryList<ChildComponent>;

@ContentChild(SomeDirective) projected!: SomeDirective;
@ContentChildren(SomeDirective) projectedList!: QueryList<SomeDirective>;
  • Query by a template reference variable name (string) or by a component/directive class.
  • @ViewChild/@ContentChild return a single match; the plural forms return a QueryList.
  • Values are undefined until the corresponding lifecycle hook (ngAfterViewInit/ngAfterContentInit) has run.
  • The modern signal-based equivalents are viewChild(), viewChildren(), contentChild(), and contentChildren().

ViewChild/ContentChild Cheatsheet

Classic decorators and their modern signal-based equivalents.

Task Classic Modern
Single view child @ViewChild(Ref) viewChild(Ref)
Multiple view children @ViewChildren(Ref) viewChildren(Ref)
Single projected child @ContentChild(Ref) contentChild(Ref)
Multiple projected children @ContentChildren(Ref) contentChildren(Ref)
Required (no undefined) n/a (always optional) viewChild.required(Ref)
Read value this.ref.nativeElement this.ref().nativeElement

Querying a Child Component Instance

Passing a component class (instead of a string) to @ViewChild lets you call public methods or read public properties directly on a specific child component instance.

@Component({ selector: 'app-modal', standalone: true, template: '<ng-content></ng-content>' })
export class ModalComponent {
  isOpen = false;
  open() { this.isOpen = true; }
  close() { this.isOpen = false; }
}

@Component({
  selector: 'app-page',
  standalone: true,
  imports: [ModalComponent],
  template: `
    <app-modal #modal></app-modal>
    <button (click)="modal.open()">Open Modal</button>
  `,
})
export class PageComponent {}

Here the template reference variable #modal is used directly in the template, avoiding a @ViewChild query entirely for this simple case.

Multiple Matches With ViewChildren

@ViewChildren returns a QueryList, an observable-like collection that updates automatically as matching elements are added or removed, useful for working with dynamic lists of child components.

@ViewChildren(TabComponent) tabs!: QueryList<TabComponent>;

ngAfterViewInit() {
  this.tabs.changes.subscribe(() => console.log('Tabs changed:', this.tabs.length));
}

Accessing Projected Content With ContentChild

@ContentChild looks for a match inside content projected into the component from its parent (via <ng-content>), rather than inside the component's own template.

@Component({ selector: 'app-card', standalone: true, template: '<ng-content></ng-content>' })
export class CardComponent implements AfterContentInit {
  @ContentChild(CardTitleDirective) title?: CardTitleDirective;

  ngAfterContentInit() {
    console.log('Projected title found:', !!this.title);
  }
}

Common Mistakes

  • Reading a @ViewChild reference in ngOnInit, before the view (and thus the reference) exists; use ngAfterViewInit instead.
  • Forgetting the non-null assertion (!) or proper typing, leading to confusing undefined errors at compile time.
  • Overusing @ViewChild for things that could be solved with simple @Input()/@Output() communication instead.
  • Not unsubscribing from QueryList.changes when a component using @ViewChildren is destroyed.

Key Takeaways

  • @ViewChild/@ViewChildren access elements, directives, or components from a component's own template.
  • @ContentChild/@ContentChildren access content projected in from a parent via <ng-content>.
  • Values are only available after the relevant lifecycle hook has run, not before.
  • Modern signal-based equivalents (viewChild(), contentChild(), etc.) integrate directly with Angular signals.

Pro Tip

Reach for @ViewChild/@ContentChild only when binding-based communication (inputs, outputs, services) can't express what you need, they create tighter coupling between parent and child than declarative bindings.