2026-05-11 19:38:21 -04:00

84 lines
2.7 KiB
Markdown

# 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)._