Files
ECC/pi/core/skills/angular-developer/references/effects.md
T
Affaan MustafaandGitHub c70874fae9 feat(pi): curated pi/core skills+prompts profile, CI load test, and 2.2.2 release sync (#3264)
Adds a curated, Pi-native, skills+prompts-only profile at pi/core/ for downstream packagers that mirror GitHub Releases.

- manifests/pi-core.json: explicit include lists, per-item exclusion reasons, curation rules, safety allowlists; every root skill and command must be classified.
- scripts/build-pi-core.js regenerates pi/core deterministically (package.json from VERSION, LICENSE, README.md, CURATION.md, skills/, commands/); --check fails CI on drift. council is renamed ecc-council inside pi/core only.
- Safety checks: no callable endpoints outside the allowlist, no npx/curl|sh/pip install, no secrets, no absolute home paths, no symlinks, valid frontmatter, no duplicate names.
- CI: build + drift check and an offline Pi CLI load test of pi/core.
- Release: VERSION, package.json and pi/core/package.json at 2.2.2, CHANGELOG, tag-triggered release verification, and a two-week cadence in CONTRIBUTING.md.

pi/core: 123 of 293 skills and 24 of 94 commands; 35,006 characters of skill description text.
2026-09-29 20:24:05 -05:00

2.7 KiB

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.

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.

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