mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-14 00:23:04 +08:00
84 lines
2.7 KiB
Markdown
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)._
|