Skip to the solution
HardFrontend9 min read

What is the difference between afterRender and afterNextRender lifecycle hooks?

423 views

The problem

Question presented to candidate: "Explain the difference between afterRender and afterNextRender. Go a level deeper than 'once versus every time': what are the phases, why do they exist, and what does 'every render' actually mean in an application with many components?"

What a strong answer should cover:

  • afterNextRender runs once; afterRender (renamed afterEveryRender in v20, the only name in 22.2.3) runs after every application render.
  • "Every render" is application-wide: the callback runs even when only some unrelated component changed.
  • The four phases in fixed order: earlyRead, write, mixedReadWrite, read; the plain-callback form runs in mixedReadWrite; each phase receives the previous phase's return value.
  • Phases are scheduled across all components (all writes before all reads) to avoid layout thrashing; Angular cannot enforce correct use.
  • afterRenderEffect as the signal-driven variant that runs only when its dependencies change.
  • Browser only, injection context required, returns an AfterRenderRef with destroy().

Clarifying questions expected:

  • "Which Angular version? The old name only exists before v20."
  • "Is the work a DOM read, a DOM write, or both?"

Code / implementation expected: Optional β€” a phased afterNextRender spec and an afterEveryRender read.

  • lifecycle
  • rendering

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

AngularPhased afterNextRender, app-wide afterEveryRender and a signal-driven afterRenderEffect
import { Component, ElementRef, ViewChild, afterEveryRender, afterNextRender, afterRenderEffect, signal } from "@angular/core";

const calls: string[] = [];

@Component({
  selector: "quiet-panel",
  template: `<p>quiet panel (its own state never changes)</p>`,
})
export class QuietPanelComponent {
  constructor() {
    afterEveryRender(() => calls.push("quiet-panel afterEveryRender"));
  }
}

@Component({
  selector: "app-root",
  imports: [QuietPanelComponent],
  template: `
    <div #box [style.width.px]="width()">box</div>
    <quiet-panel />
    <button (click)="calls.length = 0; width.set(width() + 50)">grow box</button>
    <button (click)="calls.length = 0; other.set(other() + 1)">unrelated change {{ other() }}</button>
    <button (click)="lines.set(calls.slice())">show log</button>
    <ol>
      @for (line of lines(); track $index) { <li>{{ line }}</li> }
    </ol>`,
})
export class AppComponent {
  @ViewChild("box") box!: ElementRef<HTMLElement>;
  width = signal(100);
  other = signal(0);
  calls = calls;
  lines = signal<string[]>([]);

  constructor() {
    // Runs ONCE. Each phase receives what the previous phase returned.
    afterNextRender({
      earlyRead: () => { calls.push("once: earlyRead"); return this.box.nativeElement.style.width; },
      write: (w) => { calls.push(`once: write got ${w}`); return "styled"; },
      mixedReadWrite: (s) => { calls.push(`once: mixedReadWrite got ${s}`); return 3; },
      read: (n) => calls.push(`once: read got ${n}`),
    });
    // Runs after EVERY application render, whatever caused it.
    afterEveryRender({
      read: () => calls.push(`every: read width = ${this.box.nativeElement.style.width}`),
    });
    // Runs after render only when a signal it read has changed.
    afterRenderEffect(() => calls.push(`effect: width signal = ${this.width()}`));
  }
}

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 107 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 is runInInjectionContext and when do you need to use it?

Continue