In Angular, when should you use afterRenderEffect() instead of effect(), and how do its earlyRead, write, mixedReadWrite and read phases run?
answer
- after the DOM is updated
- browser only
- four phases, fixed order
- previous phase value as a signal
- default phase is the slow one
basics
~20 sUse afterRenderEffect() when signal-driven work must touch the rendered DOM: it runs after Angular commits changes, only in the browser. Phases run earlyRead, write, mixedReadWrite, read; each receives the previous phase's result as a signal.
solid answer
~50 s`effect()` runs during change detection, **before** Angular updates the DOM, so it is the wrong place to measure layout or to drive a library that reads the DOM. `afterRenderEffect()` behaves like an effect, tracking signals and re-running when they change, but runs **after rendering has been committed**, and only in the browser, never on the server. You can pass one callback, which runs in the `mixedReadWrite` phase and is flagged in the API docs as a performance risk, or an object with `earlyRead`, `write`, `mixedReadWrite` and `read` callbacks. They run in that order, and each phase after the first receives the previous phase's return value as a `Signal`, plus a cleanup registration function. Grouping DOM reads and writes by phase lets Angular batch them across components and avoid layout thrashing. Components may not be hydrated yet when it runs.
code
ts · 23 linesimport {Component, ElementRef, afterRenderEffect, inject, input} from '@angular/core';
@Component({
selector: 'app-theme-canvas',
template: `<canvas></canvas>`,
})
export class ThemeCanvas {
readonly theme = input<'light' | 'dark'>('light');
private readonly host = inject(ElementRef<HTMLElement>);
constructor() {
afterRenderEffect({
earlyRead: () => this.host.nativeElement.clientWidth,
write: (width) => {
const canvas = this.host.nativeElement.querySelector('canvas') as HTMLCanvasElement;
canvas.width = width();
const ctx = canvas.getContext('2d')!;
ctx.fillStyle = this.theme() === 'dark' ? '#111' : '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
},
});
}
}go deeper
Recall that afterRenderEffect() runs after Angular updates the DOM and only in the browser, unlike effect().
Explain the four phases, their fixed order, how values pass between them as signals, and why the single-callback form defaults to mixedReadWrite.
Show how you split DOM reads and writes across phases to avoid layout thrashing, and handle hydration and third-party library integration safely.
Set a team rule for DOM-touching code: when effects, render hooks or platform observers are the right tool, and how to keep manual DOM work rare.
## Why `effect()` is not enough for DOM work An Angular `effect()` runs as part of change detection. For a component's effect that means **before the component's template is checked**, so before the DOM reflects the latest state. Reading an element's size there gives the previous layout; writing styles there can be overwritten by the template update that follows. `afterRenderEffect()` from `@angular/core` exists for exactly this gap. It is an effect in every reactive sense: - it tracks the signals each phase reads and re-runs when they change; - it always runs at least once; - it is created in an injection context, or with an `injector` option; - it returns an `AfterRenderRef` with `destroy()`, and accepts `manualCleanup`. But it runs **after Angular has finished rendering and committed its changes to the DOM**, and only in the browser: on the server it returns a no-op reference and never runs. ## The four phases Direct DOM access is expensive mostly because alternating reads and writes force the browser to recompute layout again and again. Phases let Angular run all components' reads together and all writes together. | Phase | Allowed | Guidance | |---|---|---| | `earlyRead` | read the DOM | only when a later write depends on it; otherwise prefer `read` | | `write` | write the DOM | **never** read in this phase | | `mixedReadWrite` | read and write | avoid whenever the work can be split | | `read` | read the DOM | **never** write in this phase | They always run in this order: 1. `earlyRead` 2. `write` 3. `mixedReadWrite` 4. `read` Angular cannot enforce the rules; it relies on you to keep reads and writes in their phases. ## Passing values between phases The first phase you define receives only a cleanup registration function. Every later phase receives the **previous defined phase's return value as a `Signal`**, followed by the cleanup function. That makes the phases a small pipeline: measure in `earlyRead`, return the measurement, apply it in `write`. ```ts afterRenderEffect({ earlyRead: () => this.host.nativeElement.getBoundingClientRect().width, write: (width) => { this.chart.resize(width(), this.theme()); }, }); ``` If a phase writes a signal that the effect tracks, the affected phases execute again. Each phase's cleanup runs before that phase re-runs and when the effect is destroyed. ## The single-callback form `afterRenderEffect(() => { ... })` is allowed, and it runs the callback in the **`mixedReadWrite`** phase. The API docs mark this with a critical note: prefer an explicit phase, or you risk significant performance degradation from extra reflows. ## Choosing between the render-time APIs | API | Runs | Tracks signals | Typical use | |---|---|---|---| | `effect()` | during change detection, before the DOM update | yes | syncing state to storage, logging, non-DOM APIs | | `afterRenderEffect()` | after rendering, when its signals changed | yes | DOM measurement or third-party DOM libraries driven by signals | | `afterNextRender()` / `afterEveryRender()` | once, or after every render | no | one-time DOM setup, or work on every render regardless of signals | A common pairing, shown in the Angular guide: create a chart instance once in `afterNextRender`, then keep it updated with `afterRenderEffect` whenever its data signal changes. For a theme, `afterRenderEffect` is the right tool when a canvas or chart library must be recoloured after the new theme has reached the DOM; persisting the theme itself is a plain `effect()`. ## Cautions - **Hydration.** Components are not guaranteed to be hydrated before the callback runs, so be careful reading or writing DOM that server rendering produced. - **Prefer platform observers.** For reacting to size or visibility changes, `ResizeObserver`, `MutationObserver` or `IntersectionObserver` are often better than polling the DOM from an effect. - **Browser-only means not on the server.** Any state the server render needs cannot come from an `afterRenderEffect`. - **Don't write state in `read`.** A signal write in a later phase re-runs affected phases and can cause another render.
- In Angular, what does afterRenderEffect() do during server-side rendering?Nothing. On the server it returns a no-op reference and its callbacks never run, so it is safe for DOM work but cannot produce state the server render depends on.
- In Angular, why does the single-callback form of afterRenderEffect() carry a performance warning?It runs in the `mixedReadWrite` phase, where reads and writes interleave. Angular cannot batch that work with other components' reads and writes, so it risks extra layout recalculations. Splitting the work into `earlyRead`/`write` or `write`/`read` avoids it.
The four phases work like a surveyor and a painter sharing a room: the surveyor measures everything first, the painter paints using those measurements, and only then does anyone re-measure, instead of measuring and painting wall by wall and waiting for paint to dry each time.
saying these in an interview costs you the question
- effect() runs after the DOM is updated, so it suits DOM measurement.
- afterRenderEffect() runs on the server too, like effect().
- Phases run in the order you write them in the object.
- The single-callback form runs in the read phase.
- Each phase receives the previous phase's plain return value, not a signal.