computed() : les valeurs dérivées

À retenircomputed() crée une valeur calculée à partir d'autres signals. Elle se recalcule automatiquement, uniquement quand une de ses dépendances change, et met son résultat en cache le reste du temps.

Une grande partie de l'état d'une application n'est pas « stockée » mais dérivée : un total se déduit d'un panier, un message d'erreur d'un champ, un libellé d'un statut. Avec les signals, on n'écrit jamais ces valeurs à la main — on les calcule avec computed().

Comment créer une valeur dérivée ?

computed() prend une fonction de calcul qui lit un ou plusieurs signals et renvoie une valeur. Le résultat est un signal en lecture seule :

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 — recalculé automatiquement

On lit un computed exactement comme un signal : en l'appelant (total()). En revanche, il n'a ni set ni update : sa valeur est entièrement déterminée par ses dépendances.

Pourquoi computed() est-il efficace ?

Deux mécanismes le rendent quasi gratuit :

  1. Détection automatique des dépendances. Angular note quels signals ont été lus pendant le calcul. Ici, total dépend de prix et quantite — sans qu'on ait à le déclarer.
  2. Mémoïsation (cache) + évaluation paresseuse. La fonction n'est ré-exécutée que si une dépendance a changé, et seulement lorsqu'on lit le résultat. Tant que rien ne bouge, total() renvoie la valeur en cache.
const a = signal(2);
const b = signal(5);
const somme = computed(() => {
  console.log('calcul de somme'); // ne s'affiche que lors d'un vrai recalcul
  return a() + b();
});

somme(); // "calcul de somme" → 7
somme(); // (rien) → 7, valeur en cache
b.set(10);
somme(); // "calcul de somme" → 12

Composer des computed entre eux

Un computed peut dépendre d'autres computed. Angular gère le graphe de dépendances et l'ordre de recalcul pour vous :

const sousTotal = computed(() => prix() * quantite());
const tva = computed(() => sousTotal() * 0.2);
const totalTTC = computed(() => sousTotal() + tva());

Changer prix invalide sousTotal, donc tva, donc totalTTC — chacun recalculé une seule fois, dans le bon ordre.

Exemple complet : un panier réactif

Voici un composant qui dérive le nombre d'articles et le total d'un signal de panier. Quand on ajoute un article, les deux valeurs se mettent à jour sans aucun appel manuel :

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>Panier</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>Nombre d'articles : {{ 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)),
    );
  }
}

Essayez en direct

Ajoutez des articles et regardez le total et le compteur se recalculer seuls :

computed-panier

Bonnes pratiques

  • Gardez la fonction pure. Un computed doit seulement calculer et renvoyer. N'y faites jamais d'effet de bord (pas de console.log métier, pas de set d'un autre signal, pas d'appel HTTP) — c'est le rôle d'effect(), vu au chapitre suivant.
  • Pas d'écriture. Un computed n'a pas de set ; s'il vous en faut un, c'est sans doute un signal writable ou un linkedSignal (chapitre 9).
  • Découpez. Plusieurs petits computed lisibles valent mieux qu'un seul énorme calcul.

Erreur fréquente : lire trop tôt

Comme les dépendances sont détectées à l'exécution, un signal lu dans une branche conditionnelle n'est suivi que si cette branche s'exécute :

const afficherTaxe = signal(false);
const montant = signal(100);

const affichage = computed(() =>
  afficherTaxe() ? montant() * 1.2 : montant(),
);
// Tant que afficherTaxe() est false, le facteur 1.2 n'entre pas en jeu,
// mais montant() est lu dans les deux branches → toujours suivi. ✅

C'est voulu et performant : seules les dépendances réellement utilisées lors du dernier calcul comptent.

En résumé

  • computed() dérive une valeur d'autres signals, en lecture seule.
  • Recalcul automatique, mémoïsé et paresseux.
  • Les computed se composent en un graphe géré par Angular.
  • La fonction de calcul doit rester pure.

Au chapitre 4, on aborde la troisième primitive, effect() : exécuter du code en réaction aux changements (log, stockage, synchronisation avec le DOM).

Nous utilisons Microsoft Clarity pour comprendre comment le site est utilisé et l'améliorer. En poursuivant votre navigation, vous l'acceptez. Vous pouvez le désactiver à tout moment.