computed(): derived values
Key takeaway —
computed()creates a value computed from other signals. It recomputes automatically, only when one of its dependencies changes, and caches its result the rest of the time.
A large part of an application's state is not "stored" but derived: a total follows from a cart, an error message from a field, a label from a status. With signals, you never write these values by hand — you compute them with computed().
How do you create a derived value?
computed() takes a compute function that reads one or more signals and returns a value. The result is a read-only signal:
import { signal, computed } from '@angular/core';
const prix = signal(10);
const quantite = signal(3);
const total = computed(() => prix() * quantite());
console.log(total()); // 30
prix.set(12);
console.log(total()); // 36 — recomputed automatically
You read a computed exactly like a signal: by calling it (total()). However, it has neither set nor update: its value is entirely determined by its dependencies.
Why is computed() efficient?
Two mechanisms make it almost free:
- Automatic dependency detection. Angular notes which signals were read during the computation. Here,
totaldepends onprixandquantite— without you having to declare it. - Memoization (cache) + lazy evaluation. The function is only re-executed if a dependency has changed, and only when you read the result. As long as nothing moves,
total()returns the cached value.
const a = signal(2);
const b = signal(5);
const somme = computed(() => {
console.log('computing sum'); // only shows on a real recompute
return a() + b();
});
somme(); // "computing sum" → 7
somme(); // (nothing) → 7, cached value
b.set(10);
somme(); // "computing sum" → 12
Composing computed with each other
A computed can depend on other computed. Angular manages the dependency graph and the recompute order for you:
const sousTotal = computed(() => prix() * quantite());
const tva = computed(() => sousTotal() * 0.2);
const totalTTC = computed(() => sousTotal() + tva());
Changing prix invalidates sousTotal, then tva, then totalTTC — each recomputed only once, in the right order.
Full example: a reactive cart
Here is a component that derives the item count and the total from a cart signal. When you add an item, both values update without any manual call:
import { Component, signal, computed, ChangeDetectionStrategy } from '@angular/core';
interface Article {
nom: string;
prix: number;
quantite: number;
}
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<h1>Cart</h1>
@for (article of articles(); track article.nom) {
<div>
{{ article.nom }} — {{ article.prix }} € × {{ article.quantite }}
<button (click)="ajouter(article.nom)">+1</button>
</div>
}
<hr />
<p>Number of items: {{ nombreArticles() }}</p>
<p><strong>Total: {{ total() }} €</strong></p>
`,
})
export class AppComponent {
readonly articles = signal<Article[]>([
{ nom: 'Clavier', prix: 49, quantite: 1 },
{ nom: 'Souris', prix: 25, quantite: 2 },
]);
readonly nombreArticles = computed(() =>
this.articles().reduce((n, a) => n + a.quantite, 0),
);
readonly total = computed(() =>
this.articles().reduce((somme, a) => somme + a.prix * a.quantite, 0),
);
ajouter(nom: string): void {
this.articles.update((liste) =>
liste.map((a) => (a.nom === nom ? { ...a, quantite: a.quantite + 1 } : a)),
);
}
}
Try it live
Add items and watch the total and the counter recompute on their own:
computed-panier
Best practices
- Keep the function pure. A
computedshould only compute and return. Never put a side effect in it (no businessconsole.log, nosetof another signal, no HTTP call) — that is the role ofeffect(), covered in the next chapter. - No writing. A
computedhas noset; if you need one, it is probably a writablesignalor alinkedSignal(chapter 9). - Split it up. Several small, readable
computedare better than one huge calculation.
Common mistake: reading too early
Since dependencies are detected at runtime, a signal read in a conditional branch is only tracked if that branch executes:
const afficherTaxe = signal(false);
const montant = signal(100);
const affichage = computed(() =>
afficherTaxe() ? montant() * 1.2 : montant(),
);
// As long as afficherTaxe() is false, the 1.2 factor never comes into play,
// but montant() is read in both branches → always tracked. ✅
This is intentional and performant: only the dependencies actually used during the last computation count.
Key points
computed()derives a value from other signals, read-only.- Recompute is automatic, memoized and lazy.
computedcompose into a graph managed by Angular.- The compute function must stay pure.
In chapter 4, we tackle the third primitive, effect(): running code in reaction to changes (log, storage, DOM synchronization).