computed() and effect()
Derive values from signals with computed(), and run side effects in response to signal changes with effect().
computed()
computed() derives a new, read-only signal from one or more other signals. Angular automatically tracks which signals it reads, and only re-evaluates it when one of those actually changes — the result is cached in between, exactly like a memoized value.
import { Component, signal, computed } from '@angular/core';
@Component({ selector: 'app-cart', standalone: true, template: `<p>Total: {{ total() }}</p>`,})export class CartComponent { price = signal(25); quantity = signal(2);
total = computed(() => this.price() * this.quantity());}Computed vs a Plain Getter
A plain TypeScript getter re-runs on every single change detection cycle, regardless of whether its inputs changed. A computed signal only re-runs when its actual dependencies change, making it more efficient for expensive derivations.
| computed() | Plain Getter | |
|---|---|---|
| Caching | Cached until a dependency signal changes | Re-evaluated on every check |
| Read syntax | total() — explicit call | total — plain property access |
| Best for | Derived values read often or expensive to compute | Trivial, cheap derivations |
effect()
effect() runs a side effect whenever any signal it reads changes — the signal-based equivalent of a watcher, for things like logging, syncing to localStorage, or calling a non-reactive API.
import { Component, signal, effect } from '@angular/core';
@Component({ selector: 'app-theme', standalone: true, template: `...` })export class ThemeComponent { theme = signal('light');
constructor() { effect(() => { localStorage.setItem('theme', this.theme()); console.log('Theme changed to', this.theme()); }); }}effect() typically needs to run inside a component's constructor (or another valid injection context) — calling it later, like inside a click handler, requires passing an explicit Injector reference.
Cleaning Up in an Effect
An effect can return a cleanup function, run automatically before the effect re-runs or when the component is destroyed — the signal-based equivalent of ngOnDestroy for a specific side effect.
effect((onCleanup) => { const id = setInterval(() => console.log(this.count()), 1000); onCleanup(() => clearInterval(id));});Common Beginner Mistakes
A computed signal must be a pure derivation with no side effects — use effect() for anything that needs to change other state or call an API.
If you're deriving a value to display, use computed() — reserve effect() for genuine side effects outside Angular's reactivity (logging, storage, non-Angular APIs).
FAQs
Yes — computed signals can read other computed signals, and Angular tracks the whole dependency chain correctly.
Different mechanisms achieving a similar goal — ngOnChanges reacts specifically to @Input changes, while effect() reacts to any signal read inside it, regardless of source.
Summary
computed() derives cached values from signals, and effect() runs side effects in response to signal changes. Next, you'll learn the signal-based equivalents of @Input and @Output.