LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2117 min read

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
CachingCached until a dependency signal changesRe-evaluated on every check
Read syntaxtotal() — explicit calltotal — plain property access
Best forDerived values read often or expensive to computeTrivial, 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() Must Be Created in an Injection Context

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

Mutating a signal inside a computed() callback

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.

Overusing effect() for things a computed() should handle

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.

Next Lesson →

Signal-based Inputs & Model