Skip to the solution
HardFrontend9 min read

viewChild() signal queries vs @ViewChild decorator — what changed?

1.1k views

The problem

Question presented to candidate: "Angular now has viewChild() and friends as functions. What changed compared with the @ViewChild decorator: the types, the timing, required queries, and how you react to changes? Would you migrate an existing component?"

What a strong answer should cover:

  • viewChild(), viewChildren(), contentChild(), contentChildren() return signals (Signal<T | undefined>, Signal<readonly T[]>); stable since v19 (developer preview in 17.2).
  • .required removes undefined from the type; reading it too early is caught by the compiler (NG8118) or throws at runtime (NG0951).
  • No static option and no QueryList: derived state is computed(), side effects are effect() / afterRenderEffect(), instead of ngAfterViewInit + changes.subscribe.
  • They compile to a different instruction (ɵɵviewQuerySignal) and need the Angular compiler: plain JIT does not register them.
  • Same locators and read option; contentChild defaults to descendants: true.
  • Migration: ng generate @angular/core:signal-queries-migration; decorators are not deprecated.

Clarifying questions expected:

  • "Is the codebase already on signal inputs, or still decorator-based?"
  • "Are there tests or libraries that read the query properties directly?"

Code / implementation expected: Yes — a component using viewChild.required, an optional viewChild inside @if, viewChildren with computed, and contentChild.

  • viewchild
  • signals
  • angular-18

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read · 10 sections

Read the code

AngularviewChild.required, an optional viewChild in @if, viewChildren with computed and a contentChild
import { Component, ElementRef, afterNextRender, computed, contentChild, effect, signal, viewChild, viewChildren } from "@angular/core";

@Component({
  selector: "search-panel",
  template: `<ng-content select="h4" /><input #search placeholder="search" />`,
})
export class SearchPanelComponent {
  // Content query: the <h4> the parent projects, as a signal.
  title = contentChild<ElementRef<HTMLElement>>("title");
  // Required view query: Signal<ElementRef>, no "| undefined" in the type.
  search = viewChild.required<ElementRef<HTMLInputElement>>("search");

  constructor() {
    afterNextRender(() => this.search().nativeElement.focus());
  }
}

@Component({
  selector: "app-root",
  imports: [SearchPanelComponent],
  template: `
    <search-panel><h4 #title>Find a user</h4></search-panel>
    @if (showHint()) { <small #hint>Press Enter to search</small> }
    @for (n of rows(); track n) { <li #row>row {{ n }}</li> }
    <button (click)="showHint.set(!showHint())">toggle hint</button>
    <button (click)="rows.set([...rows(), rows().length + 1])">add row</button>
    <p>rows = {{ rowCount() }}, hint = {{ hint() ? "shown" : "hidden" }}, panel title = {{ panelTitle() }}</p>`,
})
export class AppComponent {
  showHint = signal(false);
  rows = signal([1, 2]);

  panel = viewChild.required(SearchPanelComponent);
  hint = viewChild<ElementRef<HTMLElement>>("hint");    // Signal<ElementRef | undefined>
  rowEls = viewChildren<ElementRef<HTMLElement>>("row"); // Signal<readonly ElementRef[]>

  // Queries are signals, so derived state is just computed(): no
  // ngAfterViewInit, no QueryList.changes subscription.
  rowCount = computed(() => this.rowEls().length);
  panelTitle = computed(() => this.panel().title()?.nativeElement.textContent ?? "none");

  constructor() {
    effect(() => console.log(`effect: hint ${this.hint() ? "present" : "absent"}, rows ${this.rowEls().length}`));
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 82 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What does ChangeDetectorRef do?

Continue