Back to Angular

Side Effects with `effect` and `afterRenderEffect`

skills/dev-skills/angular-developer/references/effects.md

22.0.0-next.102.7 KB
Original Source

Side Effects with effect and afterRenderEffect

In Angular, an effect is an operation that runs whenever one or more signal values it tracks change.

When to use effect

Effects are intended for syncing signal state to imperative, non-signal APIs.

Valid Use Cases:

  • Logging analytics.
  • Syncing state to localStorage or sessionStorage.
  • Performing custom rendering to a <canvas> or 3rd-party charting library.

CRITICAL RULE: DO NOT use effects to propagate state. If you find yourself using .set() or .update() on a signal inside an effect to keep two signals in sync, you are making a mistake. This causes ExpressionChangedAfterItHasBeenChecked errors and infinite loops. Always use computed() or linkedSignal() for state derivation.

Basic Usage

Effects execute asynchronously during the change detection process. They always run at least once.

ts
import { Component, signal, effect } from '@angular/core';

@Component({...})
export class Example {
  count = signal(0);

  constructor() {
    // Effect must be created in an injection context (e.g., a constructor)
    effect((onCleanup) => {
      console.log(`Count changed to ${this.count()}`);

      const timer = setTimeout(() => console.log('Timer finished'), 1000);

      // Cleanup function runs before the next execution, or when destroyed
      onCleanup(() => clearTimeout(timer));
    });
  }
}

DOM Manipulation with afterRenderEffect

Standard effect runs before Angular updates the DOM. If you need to manually inspect or modify the DOM based on a signal change (e.g., integrating a 3rd party UI library), use afterRenderEffect.

afterRenderEffect runs after Angular has finished rendering the DOM.

Render Phases

To prevent reflows (forced layout thrashing), afterRenderEffect forces you to divide your DOM reads and writes into specific phases.

ts
import { Component, afterRenderEffect, viewChild, ElementRef } from '@angular/core';

@Component({...})
export class Chart {
  canvas = viewChild.required<ElementRef>('canvas');

  constructor() {
    afterRenderEffect({
      // 1. Read from the DOM
      earlyRead: () => {
        return this.canvas().nativeElement.getBoundingClientRect().width;
      },
      // 2. Write to the DOM (receives the result of the previous phase)
      write: (width) => {
        // NEVER read from the DOM in the write phase.
        setupChart(this.canvas().nativeElement, width);
      }
    });
  }
}

Available Phases (executed in this order):

  1. earlyRead
  2. write (Never read here)
  3. mixedReadWrite (Avoid if possible)
  4. read (Never write here)

Note: afterRenderEffect only runs on the client, never during Server-Side Rendering (SSR).