computed(): derived values

Key takeawaycomputed() 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:

  1. Automatic dependency detection. Angular notes which signals were read during the computation. Here, total depends on prix and quantite — without you having to declare it.
  2. 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 computed should only compute and return. Never put a side effect in it (no business console.log, no set of another signal, no HTTP call) — that is the role of effect(), covered in the next chapter.
  • No writing. A computed has no set; if you need one, it is probably a writable signal or a linkedSignal (chapter 9).
  • Split it up. Several small, readable computed are 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.
  • computed compose 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).

We use Microsoft Clarity to understand how the site is used and improve it. By continuing to browse, you accept it. You can disable it at any time.