Retour au blog
oct. 2026·3 min de lectureGénéré par IA
Langue:DeutschEnglishFrançaisÖsterreichischKlingonian Version originale: English

Comprendre les Signals dans Angular

#angular#typescript#frontend

La détection de changements d'Angular fonctionne historiquement en vérifiant l'arbre entier des composants dès que quelque chose a pu se produire : un événement, un timer résolu, une réponse HTTP. Les Signals remplacent ce balayage large, basé sur les zones, par quelque chose de bien plus précis : des valeurs réactives qui savent exactement quelles parties d'un template en dépendent, de sorte que seules ces parties doivent être mises à jour.

Le problème que résolvent les Signals

L'Angular classique s'appuie sur Zone.js pour patcher les API asynchrones (setTimeout, addEventListener, fetch, etc.) et savoir quand lancer la détection de changements. Mais il ne sait pas ce qui a réellement changé : sa stratégie par défaut consiste donc à revérifier chaque composant depuis la racine, en comparant anciennes et nouvelles valeurs par référence. La détection OnPush réduit cela en ignorant les composants dont les références @Input() n'ont pas changé, mais il faut l'activer explicitement, et veiller à toujours produire de nouvelles références d'objet ou de tableau quand les données changent.

// Without OnPush: this component gets re-checked on every zone event,
// anywhere in the app, whether count actually changed or not
@Component({
  selector: 'app-counter',
  template: `<p>Count: {{ count }}</p>`,
})
export class CounterComponent {
  count = 0;
}

Un signal est un conteneur réactif

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

const count = signal(0);

console.log(count()); // 0 — signals are read by calling them as functions
count.set(1);
count.update((n) => n + 1);
console.log(count()); // 2

L'essentiel n'est pas la syntaxe getter/setter : c'est que lire un signal dans un contexte réactif (un template, un computed(), un effect()) enregistre automatiquement ce contexte comme dépendant. Quand le signal change, Angular sait précisément quels templates et quels calculs en dépendent, sans que Zone.js doive deviner en revérifiant tout.

@Component({
  selector: 'app-counter',
  template: `
    <p>Count: {{ count() }}</p>
    <button (click)="increment()">+1</button>
  `,
})
export class CounterComponent {
  count = signal(0);
  increment() {
    this.count.update((n) => n + 1);
  }
}

État dérivé avec computed()

computed() crée un signal dont la valeur est dérivée d'autres signals, et qui se recalcule automatiquement, et uniquement, lorsqu'une de ses dépendances change réellement.

const price = signal(29.99);
const quantity = signal(3);

const total = computed(() => price() * quantity());

console.log(total()); // 89.97
quantity.set(5);
console.log(total()); // 149.95

Point crucial : computed() est paresseux et mémoïsé. Il ne se recalcule pas à chaque lecture, mais seulement si une dépendance a changé depuis la dernière lecture. Lire total() deux fois de suite sans toucher à price ou quantity entre-temps renvoie les deux fois la valeur en cache.

Effets de bord avec effect()

Parfois, il faut faire quelque chose quand un signal change (le journaliser, le synchroniser avec localStorage, déclencher une animation) plutôt qu'en dériver une nouvelle valeur. C'est le rôle de effect() :

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

const theme = signal<'light' | 'dark'>('light');

effect(() => {
  document.body.className = theme(); // reruns whenever theme() changes
  localStorage.setItem('theme', theme());
});

effect() s'exécute une première fois immédiatement pour établir ses dépendances, puis se relance chaque fois qu'un signal lu pendant cette exécution change. Comme pour computed(), le suivi des dépendances est automatique : vous ne déclarez jamais de tableau de dépendances ; le framework le déduit des signals que vous avez réellement appelés dans le corps de la fonction.

Signals et OnPush fonctionnent ensemble, automatiquement

Un composant qui utilise des signals dans son template bénéficie de mises à jour fines sans activer manuellement OnPush ni gérer à la main des références immuables : Angular peut ignorer complètement la revérification d'un composant si aucun des signals qu'il lit n'a changé, car il sait exactement de quels signals dépend chaque expression de template.

@Component({
  selector: 'app-cart-total',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<p>Total: {{ total() | currency }}</p>`,
})
export class CartTotalComponent {
  price = input.required<number>();
  quantity = input.required<number>();
  total = computed(() => this.price() * this.quantity());
}

input() (l'API d'entrées d'Angular basée sur les signals) fait des liaisons @Input() elles-mêmes des signals : lorsqu'un parent met à jour une entrée liée, la valeur passe automatiquement par computed() jusqu'au template avec le même suivi fin, sans comparaison manuelle dans ngOnChanges.

Ce que les Signals ne remplacent pas

Les Signals modélisent un état synchrone et réactif ; ils ne remplacent pas les Observable de RxJS, conçus pour modéliser des flux d'événements asynchrones (réponses HTTP, messages WebSocket, flux de saisie à débouncer ou à traiter avec switchMap, etc.). Angular fournit toSignal() et toObservable() précisément parce que les deux modèles sont utiles à des fins différentes : choisissez un Observable pour composer des flux d'événements asynchrones, et un signal pour représenter une valeur courante à laquelle un template doit réagir.

À retenir

La détection de changements basée sur Zone.js demande « est-il arrivé quelque chose ? » et revérifie largement pour le savoir. Les Signals inversent la logique : chaque signal sait exactement quels calculs et quels templates en dépendent, donc les mises à jour sont ciblées au lieu d'être balayées. Cette précision permet aussi le mode entièrement sans zone d'Angular : une fois que la détection de changements est pilotée par des signals qui indiquent précisément ce qui a changé, il ne reste plus rien à patcher pour Zone.js.

Plus ancienFilme an ihren Trailern erkennen