Skip to the solution
MediumFrontend10 min read

What are Angular lifecycle hooks?

935 views

The problem

Question presented to candidate: "Name Angular's lifecycle hooks, tell me in what order they run for a parent with a child component, which ones run once and which run on every change detection pass, and how the newer render hooks and DestroyRef fit in."

What a strong answer should cover:

  • The eight class hooks in order: ngOnChanges, ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked, ngOnDestroy; which are "once" and which are "every check".
  • Parent + child interleaving: the parent reaches its content hooks before the child is even constructed, and the parent ngAfterViewInit runs only after the whole child subtree finished.
  • Destruction runs child first, and DestroyRef.onDestroy callbacks run right after the matching ngOnDestroy.
  • The function-based render hooks (afterNextRender, afterEveryRender) run after the whole application rendered, browser only.
  • With OnPush as the default in v22, a skipped component still gets ngDoCheck from its parent, but nothing below it runs.
  • What each hook is for (inputs, init, projected content, own view, cleanup) and why ngDoCheck / ...Checked hooks must be cheap.

Clarifying questions expected:

  • "Do you want the class-based hooks only, or also the signal-era render hooks?"
  • "Are the components OnPush (the v22 default) or Eager?"

Code / implementation expected: Optional β€” a parent and child that log every hook, or the order written out from memory.

  • lifecycle

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

10 min read Β· 10 sections

Read the code

AngularA parent and child that log every lifecycle hook: first render, input change, removal
import {
  AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, DoCheck,
  Input, OnChanges, OnDestroy, OnInit, SimpleChanges, signal,
} from "@angular/core";

// Hooks run DURING change detection, so they write to a plain array.
// The "show log" button copies it into a signal for display.
const calls: string[] = [];
const log = (who: string, hook: string) => calls.push(`${who} ${hook}`);

@Component({
  selector: "child-cmp",
  template: `<p>child sees {{ value }}</p><ng-content />`,
})
export class ChildComponent
  implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy
{
  @Input() value = 0;
  constructor() { log("child", "constructor"); }
  ngOnChanges(c: SimpleChanges) { log("child", "ngOnChanges(" + Object.keys(c).join(",") + ")"); }
  ngOnInit() { log("child", "ngOnInit"); }
  ngDoCheck() { log("child", "ngDoCheck"); }
  ngAfterContentInit() { log("child", "ngAfterContentInit"); }
  ngAfterContentChecked() { log("child", "ngAfterContentChecked"); }
  ngAfterViewInit() { log("child", "ngAfterViewInit"); }
  ngAfterViewChecked() { log("child", "ngAfterViewChecked"); }
  ngOnDestroy() { log("child", "ngOnDestroy"); }
}

@Component({
  selector: "parent-cmp",
  imports: [ChildComponent],
  template: `
    @if (show) {
      <child-cmp [value]="count"><em>projected</em></child-cmp>
    }`,
})
export class ParentComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterViewInit, OnDestroy {
  @Input() count = 0;
  @Input() show = true;
  constructor() { log("parent", "constructor"); }
  ngOnChanges(c: SimpleChanges) { log("parent", "ngOnChanges(" + Object.keys(c).join(",") + ")"); }
  ngOnInit() { log("parent", "ngOnInit"); }
  ngDoCheck() { log("parent", "ngDoCheck"); }
  ngAfterContentInit() { log("parent", "ngAfterContentInit"); }
  ngAfterViewInit() { log("parent", "ngAfterViewInit"); }
  ngOnDestroy() { log("parent", "ngOnDestroy"); }
}

@Component({
  selector: "app-root",
  imports: [ParentComponent],
  template: `
    <parent-cmp [count]="count()" [show]="show()" />
    <button (click)="calls.length = 0; count.set(count() + 1)">change input</button>
    <button (click)="calls.length = 0; show.set(false)">remove child</button>
    <button (click)="lines.set(calls.slice())">show log</button>
    <ol>
      @for (line of lines(); track $index) { <li>{{ line }}</li> }
    </ol>`,
})
export class AppComponent {
  count = signal(1);
  show = signal(true);
  calls = calls;
  lines = signal<string[]>([]);
}

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 26 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 nextMedium

What is the role of NG_VALUE_ACCESSOR in a custom component?

Continue