effect() : réagir aux changements

À retenireffect() exécute du code à chaque fois qu'un signal qu'il lit change. C'est l'outil des effets de bord (log, stockage, DOM), jamais celui pour calculer une valeur — ça, c'est le rôle de computed().

Après signal (la source) et computed (la dérivation), voici effect : la primitive qui agit sur le monde extérieur quand l'état change. Journaliser, écrire dans localStorage, synchroniser un élément du DOM, déclencher une analytics : tout cela passe par effect().

Comment fonctionne un effect ?

effect() prend une fonction. Angular l'exécute une première fois, note les signals lus, puis la ré-exécute à chaque fois que l'un d'eux change :

import { signal, effect } from '@angular/core';

const utilisateur = signal('Ada');

effect(() => {
  console.log('Utilisateur courant :', utilisateur());
});
// Log immédiat : "Utilisateur courant : Ada"

utilisateur.set('Linus');
// Log automatique : "Utilisateur courant : Linus"

Comme pour computed, les dépendances sont détectées automatiquement : pas besoin de lister les signals surveillés.

Où déclarer un effect ?

Un effect() doit être créé dans un contexte d'injection : le plus souvent dans le constructeur d'un composant/service, ou dans un initialiseur de champ. Il est alors automatiquement détruit avec le composant — aucun désabonnement manuel.

import { Component, signal, effect, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-root',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h1>Thème actuel : {{ theme() }}</h1>
    <button (click)="basculer()">Basculer le thème</button>
  `,
})
export class AppComponent {
  readonly theme = signal<'clair' | 'sombre'>('clair');

  constructor() {
    effect(() => {
      const t = this.theme();
      document.body.style.background = t === 'sombre' ? '#111827' : '#ffffff';
      document.body.style.color = t === 'sombre' ? '#e5e7eb' : '#1f2937';
      localStorage.setItem('theme', t);
      console.log('Thème appliqué :', t);
    });
  }

  basculer(): void {
    this.theme.update((t) => (t === 'clair' ? 'sombre' : 'clair'));
  }
}

Essayez en direct

Basculez le thème, ouvrez la console : l'effect se relance et applique le style au body à chaque changement.

effect-theme

Nettoyage : onCleanup

Un effect peut avoir besoin de nettoyer la fois précédente avant de se relancer (annuler un timer, fermer une connexion). La fonction reçoit un onCleanup pour cela :

effect((onCleanup) => {
  const id = setInterval(() => console.log('tick', compteur()), 1000);
  onCleanup(() => clearInterval(id)); // exécuté avant la prochaine exécution ET à la destruction
});

Le callback de onCleanup est appelé avant chaque ré-exécution et à la destruction de l'effect : de quoi éviter les fuites mémoire.

Quand NE PAS utiliser effect()

C'est le point le plus important du chapitre. effect() est souvent mal employé là où un computed conviendrait. Demandez-vous : « est-ce que je calcule une valeur, ou est-ce que j'agis sur l'extérieur ? »

// ❌ Anti-patron : utiliser un effect pour dériver une valeur dans un autre signal.
const prix = signal(100);
const ttc = signal(0);
effect(() => ttc.set(prix() * 1.2)); // fragile, redondant, difficile à suivre

// ✅ Correct : c'est une dérivation → computed.
const ttcOk = computed(() => prix() * 1.2);

Écrire dans un signal depuis un effect est d'ailleurs interdit par défaut (Angular lève une erreur) pour vous protéger des boucles infinies et des dépendances circulaires.

Réservez effect() aux vrais effets de bord :

  • journalisation, analytics, télémétrie ;
  • lecture/écriture dans localStorage ou sessionStorage ;
  • manipulation directe du DOM hors template (focus, mesure, librairie tierce) ;
  • synchronisation avec une API impérative non réactive.

untracked() : lire sans créer de dépendance

Parfois on veut lire un signal dans un effect sans que sa modification ne relance l'effect. untracked() le permet :

import { untracked } from '@angular/core';

effect(() => {
  const valeur = formulaire(); // dépendance suivie
  // On lit l'identifiant utilisateur sans en dépendre :
  const id = untracked(() => utilisateurId());
  console.log(`Sauvegarde du formulaire pour ${id}`, valeur);
});

Ici, l'effect ne se relance que lorsque formulaire change, pas lorsque utilisateurId change.

En résumé

  • effect() réagit aux changements de signals pour produire des effets de bord.
  • À créer dans un contexte d'injection ; détruit automatiquement.
  • onCleanup nettoie avant chaque relance et à la destruction.
  • Ne dérivez pas une valeur avec un effect → utilisez computed.
  • untracked() lit un signal sans s'y abonner.

Vous maîtrisez les trois primitives. Place au quiz des fondamentaux pour les ancrer, avant d'attaquer la communication entre composants.

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.