Présentation : Réactivité avec Angular

Angular Signals

Par Théo Beaudenon

La Réactivité en Bref

Qu’est-ce que la programmation réactive ?

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.

Pourquoi c’est important ?

Expérience utilisateur fluide

L'interface réagit immédiatement aux actions, sans attente ni latence.

Mises à jour automatiques

L'UI est toujours synchronisée avec l'état, sans intervention manuelle.

Code plus clair et maintenable

Une logique déclarative qui simplifie la lecture et la gestion du code.

Performance optimisée

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.

Les Fondamentaux : Pull vs. Push

Imaginez un chef cuisinier (le producteur) et un client (le consommateur).

Modèle Pull : Le client demande

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.

🍽️
Client
Prêt ?
🍔
👨‍🍳
Chef

Modèle Push : Le chef envoie

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.).

👨‍🍳
Chef
🍔
🍽️

Client

Pull vs. Push en Code

Comment ces modèles se traduisent-ils en code Angular ?

Pull (Signaux)

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
            

Push (RxJS)

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)
              // -> ...
            

Un peu d'histoire

La réactivité n'est pas une idée nouvelle.

🤔 Mais à votre avis, d'où vient-elle ?

L'origine : les tableurs

Image d'un tableur

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.

RxJS et le modèle "Push"

Diagramme RxJS

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.

Signaux Angular et le modèle "Pull"

Diagramme Pull

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.

Pourquoi les Signaux maintenant ?

1 : Préparer le futur sans Zone.js

Historiquement, Angular utilisait Zone.js pour la détection de changements.

Zone.js : Vérification Globale

App
Header
UserList
User (changed)
User
Footer

Un changement déclenche une vérification de tout l'arbre.

Signaux : Mise à jour Ciblée

App
Header
UserList
User (updated)
User
Footer

Seul le composant dépendant du signal est mis à jour.

2 : Les signaux permettent des gains de performance significatifs

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.

3 : Améliorer la DX (Developer Experience)

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.

4 : S’aligner avec les standards modernes du web

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.

Les bases des Signaux

Trois concepts clés :

signal() computed() effect()

1. `signal()` : La source de vérité

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());

2. `computed()` : La valeur dérivée

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());

3. `effect()` : L'effet de bord

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.).

Effet: Le statut est "loading"
const statusSignal = signal('loading');
effect(() => {
  // Ce code s'exécute quand 'statusSignal' change
  console.log(`Le statut est ${statusSignal()}`);
});
        

`effect()` avec Chart.js


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);
          
        

Signaux vs. RxJS : Choisir le bon outil

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.

Tableau Comparatif

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`

Interopérabilité avec RxJS

`toSignal` permet de convertir un Observable en Signal.

Compteur avec `interval` de RxJS

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 });

Playground : `httpResource` en action

Exemple GET : Récupérer une liste d'utilisateurs

<

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) { ... }

Propriétés de httpResource

Signaux

  • value signal
  • error signal
  • isLoading signal
  • status signal
  • headers signal
  • statusCode signal
  • progress signal

Méthodes

  • 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.

Exemple GET Réactif : Déclencher une requête via un Signal

Utilisateur ID: 1

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()}`);
               

Exemple POST : Créer un nouveau Post

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 });
         }
         

Pièges à éviter

Piège n°1 : Modifier un objet directement

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' }));

Piège n°2 : Oublier `computed`

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 !

Piège n°3 : Utiliser `effect` pour modifier un autre signal

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

Merci !

Des questions ?