Par Théo Beaudenon
C'est l'art de construire un système qui
s'adapte tout seul.
Au lieu de vérifier constamment si une donnée a changé, votre code
attend simplement d'être notifié de ce changement.
L'interface réagit immédiatement aux actions, sans attente ni latence.
L'UI est toujours synchronisée avec l'état, sans intervention manuelle.
Une logique déclarative qui simplifie la lecture et la gestion du code.
Mises à jour granulaires et ciblées pour éviter tout travail inutile.
Aujourd’hui, Angular 19 propose une nouvelle façon de gérer tout ça :
Les Signaux.
Imaginez un chef cuisinier (le producteur) et un client (le consommateur).
Le client (le consommateur) demande au chef si le plat est prêt. C'est le consommateur qui initie la communication.
Parfait pour représenter un état qui change en réponse à une action, comme le contenu d'un panier.
Le chef (le producteur) prévient le client dès que le plat est prêt, sans que celui-ci ait à demander. Le producteur initie la communication.
Idéal pour gérer des flux d'événements asynchrones (clics, réponses HTTP, etc.).
Comment ces modèles se traduisent-ils en code Angular ?
Le consommateur lit la valeur quand il en a besoin. C'est une lecture synchrone et explicite.
// Le producteur contient une valeur.
const nom = signal('Théo');
// Le consommateur "tire" la valeur.
console.log(`Bonjour, ${nom()}`); // -> Bonjour, Théo
// La mise à jour est aussi explicite.
nom.set('Alice');
console.log(`Bonjour, ${nom()}`); // -> Bonjour, Alice
Le producteur "pousse" les valeurs au consommateur, qui s'est abonné pour les recevoir. C'est une approche asynchrone.
// Le producteur émet des valeurs dans le temps.
const horloge$ = interval(1000);
// Le consommateur s'abonne et réagit aux valeurs poussées.
horloge$.subscribe(seconde => {
console.log(`Il est ${seconde} seconde(s).`);
});
// -> Il est 0 seconde(s).
// -> Il est 1 seconde(s). (1s plus tard)
// -> ...
La réactivité n'est pas une idée nouvelle.
🤔 Mais à votre avis, d'où vient-elle ?
Imaginez une feuille de calcul : vous changez une cellule, et comme par
magie,
toutes les cellules qui en dépendent se mettent à jour instantanément.
C'est le principe fondamental de la réactivité : une chaîne de dépendances
qui se met à jour automatiquement,
sans effort manuel.
Le web a apporté son lot de défis : comment gérer des flux de données continus
comme les clics de souris ou les réponses de serveurs ?
RxJS a apporté la solution avec les
Observables.
C'est le modèle "Push" : le producteur de données
(l'Observable) pousse activement les informations vers les consommateurs qui se sont
abonnés, dès qu'elles sont disponibles.
Avec les Signaux, Angular revient à la
simplicité et à l'efficacité du tableur.
C'est le modèle "Pull" : la valeur n'est pas "poussée" en
permanence.
Au lieu de cela, le consommateur "tire" (demande) la valeur uniquement lorsqu'il en a besoin.
Cette approche est incroyablement performante pour synchroniser l’état de
l’interface utilisateur.
Historiquement, Angular utilisait Zone.js pour la détection de changements.
Un changement déclenche une vérification de tout l'arbre.
Seul le composant dépendant du signal est mis à jour.
Avec les Signaux, la détection des changements est chirurgicale. Seuls les composants qui écoutent un signal sont mis à jour.
👉 On ne met à jour que ce qui est nécessaire.
Le code est plus facile à lire : on voit clairement où une valeur est définie (.set()) et où elle est lue (monSignal()).
Plus besoin de RxJS pour les besoins simples de réactivité locale.
D’autres frameworks modernes (comme Vue, React, etc.) utilisent déjà des approches réactives avec des Signaux. Angular veut rester compétitif en adoptant une API réactive déclarative et explicite.
Trois concepts clés :
signal()
computed()
effect()
Une boîte qui contient une valeur.
Mon compteur est à : 0
const counter = signal(0);
// Pour changer la valeur
counter.update(v => v + 1);
// Pour lire la valeur
console.log(counter());
Une valeur calculée automatiquement à partir d'autres signaux.
Total : 20€
const quantity = signal(2);
const price = signal(10);
// La valeur totale est toujours à jour
const total = computed(() => quantity() * price());
Une fonction qui s'exécute quand un signal qu'elle utilise change. Pour les actions qui ne modifient pas d'autres signaux (DOM, logs, etc.).
const statusSignal = signal('loading');
effect(() => {
// Ce code s'exécute quand 'statusSignal' change
console.log(`Le statut est ${statusSignal()}`);
});
const chartValueSignal = signal(50);
const myChart = new Chart(ctx, { ... });
effect(() => {
// Ce code s'exécute quand 'chartValueSignal' change
const newValue = chartValueSignal();
myChart.data.datasets[0].data[0] = newValue;
myChart.update(); // Mettre à jour le graphique
});
// Changer la valeur du signal pour déclencher l'effet
chartValueSignal.set(75);
Les Signaux et RxJS ne sont pas en concurrence. Pensez-y plutôt comme à une boîte à outils : chaque outil a son utilité. Utilisez ce guide pour savoir lequel choisir en fonction de votre besoin.
| Caractéristique | Signaux | RxJS |
|---|---|---|
| Modèle | Pull (synchrone) | Push (asynchrone) |
| Usage | État de l'UI | Événements, HTTP |
| API | Simple (`.set()`, `.value()`, `computed()`, `effect()`) | Riche (opérateurs) (`map`, `filter`, `debounceTime`, etc.) |
| Interop. | `toObservable` | `toSignal` |
`toSignal` permet de convertir un Observable en Signal.
Valeur du flux RxJS : -
const counter$ = interval(1000);
// On le convertit en signal.
// La valeur initiale est `null` jusqu'à la première émission.
const counterSignal = toSignal(counter$, { initialValue: null });
Le code pour une simple requête GET est très concis :
import { httpResource } from '@angular/http';
// La ressource est définie et la requête est lancée immédiatement.
const usersResource = httpResource('https://api.example.com/users');
// Dans le template, on peut utiliser les signaux :
// @if (usersResource.isLoading()) { Chargement...
}
// @if (usersResource.value(); as users) { ... }
value
signal
error
signal
isLoading
signal
status
signal
headers
signal
statusCode
signal
progress
signal
reload()
Méthode
set()
Méthode
update()
Méthode
hasValue()
Méthode
destroy()
Méthode
asReadonly()
Méthode
Chacune de ces propriétés nous fournit des informations spécifiques concernant la ressource http à gérer.
Le code utilise une fonction qui retourne l'URL. httpResource
détecte
que userId() est un signal et réagit à ses changements :
const userId = signal(1);
// La requête est relancée à chaque changement de `userId`
const userResource = httpResource(() => `https://.../users/${userId()}`);
httpResource n'est pas limité aux requêtes GET. En passant un objet de configuration, vous
pouvez
effectuer n'importe quel type de requête HTTP, comme POST pour créer de nouvelles données.
// La ressource est définie mais inactive car aucune donnée n'est fournie.
const newPostResource = httpResource< Post >({
method: 'POST',
url: 'https://api.example.com/posts'
});
// Pour envoyer les données, on pourrait utiliser une méthode
// comme .set() ou une fonction dédiée qui prend les données.
function createPost(title: string, body: string) {
// Cette partie est une simplification conceptuelle
newPostResource.set({ title, body });
}
La réactivité ne sera pas déclenchée. Il faut créer une nouvelle instance.
// À éviter :
const user = signal({ name: 'Bob' });
user().name = 'Alice'; // Ne fonctionne pas !
// La bonne pratique :
const user = signal({ name: 'Bob' });
user.update(u => ({ ...u, name: 'Alice' }));
Ne recalculez pas une valeur dérivée à la main. C'est le rôle de `computed`.
// À éviter :
let sum = a() + b();
// Ne se met pas à jour
// La bonne pratique :
const sum = computed(() => a() + b());
// Toujours à jour !
Un `effect` est pour les "effets de bord", pas pour changer l'état. Risque de boucles infinies.
// À éviter :
effect(() => {
b.set(a() * 2); // Risqué !
});
// La bonne pratique :
const b = computed(() => a() * 2);
// Correct et performant
Des questions ?