Skip to content

Les signaux, computed et effects dans Angular 16+

Pourquoi Angular contient désormais la notion des signaux ?

  1. Meilleures performances d'exécution : Les signaux permettent de réduire le nombre de calculs effectués lors de la détection des changements dans une application Angular. Cela se traduit par de meilleures performances d'exécution, ce qui signifie que l'application fonctionnera plus rapidement et de manière plus fluide pour les utilisateurs.

  2. Modèle mental plus simple pour la réactivité : Les signaux offrent un modèle mental plus simple pour comprendre et gérer la réactivité dans une application. Ils permettent de clarifier les dépendances de la vue (ce qui influence quoi) et le flux des données à travers l'application, ce qui facilite la compréhension du fonctionnement de l'application.

  3. Réactivité fine-grainée : Avec les signaux, il est possible d'effectuer une réactivité fine-grainée. Cela signifie que dans les versions futures d'Angular, il sera possible de vérifier les changements uniquement dans les composants concernés, ce qui améliorera les performances et l'efficacité de l'application.

  4. Réduction de la dépendance à Zone.js : Les signaux permettent de rendre Zone.js facultatif dans les versions futures d'Angular. Zone.js est une bibliothèque utilisée par Angular pour détecter les changements et exécuter les tâches asynchrones. En utilisant les signaux, Angular peut être notifié directement lorsque le modèle de données a changé, ce qui réduit la dépendance à Zone.js et peut potentiellement améliorer les performances.

  5. Propriétés calculées sans pénalité de recomputation : Les signaux permettent de définir des propriétés calculées qui sont mises à jour automatiquement lorsque les valeurs dont elles dépendent changent. Cela évite de devoir recalculer ces propriétés à chaque cycle de détection des changements, ce qui améliore les performances et l'efficacité de l'application.

  6. Meilleure interoperabilité avec RxJS : Les signaux facilitent l'interopérabilité avec RxJS, une bibliothèque de programmation réactive très populaire. Cela permet d'utiliser les fonctionnalités avancées de RxJS en combinaison avec les signaux, offrant ainsi une plus grande flexibilité et des possibilités étendues de gestion des données réactives dans l'application.

Qu'est qu'un signal ?

Le concept de signal dans Angular est une fonctionnalité introduite dans la version 16 de la bibliothèque @angular/core. Il permet de définir des valeurs réactives et d'exprimer des dépendances entre ces valeurs.

Voici quelques utilisations courantes des signaux :

  1. Gestion des états : Les signaux peuvent être utilisés pour représenter l'état d'un composant ou d'une fonctionnalité spécifique. Lorsqu'un signal est mis à jour, les parties de l'application qui dépendent de ce signal peuvent être notifiées et réagir en conséquence.

  2. Déclenchement d'effets : Les signaux peuvent être utilisés pour déclencher des effets ou des actions spécifiques lorsqu'une valeur change. Par exemple, vous pouvez définir un effet qui s'exécute chaque fois qu'un signal particulier est mis à jour, ce qui permet de réaliser des actions supplémentaires telles que l'envoi de données à un serveur ou la mise à jour de l'interface utilisateur.

  3. Gestion des dépendances : Les signaux permettent de définir des dépendances entre différentes valeurs réactives. Lorsqu'un signal sur lequel dépend un autre signal est mis à jour, cela déclenche automatiquement la mise à jour des signaux dépendants.

  4. Calcul de valeurs dérivées : Les signaux peuvent être utilisés pour calculer des valeurs dérivées basées sur d'autres signaux. Par exemple, vous pouvez définir un signal qui représente la somme de deux autres signaux, et chaque fois que les signaux d'origine sont mis à jour, le signal calculé est automatiquement mis à jour.

Bien comprendre

Un signal est une représentation d'une information qui peut changer au fil du temps. Pour mieux comprendre ce concept, prenons un exemple concret de la vie réelle.

Imaginez que vous avez un indicateur lumineux sur votre téléphone portable pour vous informer de la quantité de batterie restante. Ce voyant lumineux peut être considéré comme un signal de la batterie.

Lorsque votre téléphone est complètement chargé, le signal de la batterie peut être vert pour indiquer que la batterie est pleine. À mesure que vous utilisez votre téléphone et que la charge diminue, le signal peut passer au jaune pour indiquer un niveau de batterie moyen. Enfin, lorsque votre batterie est presque épuisée, le signal peut devenir rouge pour vous avertir que vous devez recharger votre téléphone.

Dans cet exemple, le signal de la batterie est une représentation visuelle de l'état de la batterie. Il change en fonction du niveau de charge de la batterie. Vous pouvez considérer ce signal comme une valeur réactive qui évolue en fonction d'un état spécifique.

De manière plus générale, dans le développement logiciel, un signal est utilisé pour représenter des informations qui peuvent changer au fil du temps, comme l'état d'un bouton, le nombre de likes sur un article, ou la position d'un objet sur une carte. Les signaux sont utilisés pour suivre ces changements et réagir en conséquence dans une application.

signal()

Imaginons que nous avons une application de liste de tâches (to-do list). Chaque tâche peut être marquée comme terminée ou non terminée. Lorsqu'une tâche est terminée, nous voulons automatiquement mettre à jour le compteur de tâches terminées affiché à l'utilisateur.

Pour gérer cela avec des signaux, nous pouvons utiliser la fonction signal pour créer un signal appelé isTaskCompleted qui représente l'état d'une tâche spécifique. Par exemple :

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

// Création d'un signal pour l'état de la tâche
const isTaskCompleted = signal(false);
import { signal } from '@angular/core';

// Création d'un signal pour l'état de la tâche
const isTaskCompleted = signal(false);

Dans cet exemple, nous créons un signal isTaskCompleted avec une valeur par défaut de false, ce qui signifie que la tâche n'est pas terminée initialement.

Maintenant, supposons que nous avons une fonction qui gère le marquage d'une tâche comme terminée :

typescript
function markTaskAsCompleted() {
  // Mettre à jour la valeur du signal
  isTaskCompleted.set(true);
}
function markTaskAsCompleted() {
  // Mettre à jour la valeur du signal
  isTaskCompleted.set(true);
}

Lorsque cette fonction est appelée, elle met à jour la valeur du signal isTaskCompleted en le définissant sur true, indiquant que la tâche est maintenant terminée.

Pour lire la valeur, utilisez juste dans le code:

typescript
isTaskCompleted()
isTaskCompleted()

computed()

Maintenant, nous pouvons créer un autre signal appelé completedTasksCount qui représente le nombre total de tâches terminées dans notre liste :

typescript
import { computed } from '@angular/core';

// Création d'un signal calculé pour le nombre de tâches terminées
const completedTasksCount = computed(() => {
  // Logique de calcul du nombre de tâches terminées
  const tasks = getAllTasks();
  return tasks.filter(task => task.isCompleted).length;
});
import { computed } from '@angular/core';

// Création d'un signal calculé pour le nombre de tâches terminées
const completedTasksCount = computed(() => {
  // Logique de calcul du nombre de tâches terminées
  const tasks = getAllTasks();
  return tasks.filter(task => task.isCompleted).length;
});

Dans cet exemple, nous utilisons la fonction computed pour créer un signal calculé completedTasksCount qui dépend du signal isTaskCompleted ainsi que d'autres signaux non montrés ici. Ce signal calculé utilise une logique de calcul pour déterminer le nombre de tâches terminées dans notre liste de tâches.

Maintenant, chaque fois que la valeur du signal isTaskCompleted est mise à jour, le signal calculé completedTasksCount est automatiquement recalculé en fonction de la nouvelle valeur. Ainsi, lorsque nous marquons une tâche comme terminée en utilisant la fonction markTaskAsCompleted, le signal completedTasksCount est mis à jour pour refléter le nouveau nombre de tâches terminées.

Finalement, nous pouvons utiliser la valeur du signal completedTasksCount pour afficher le nombre de tâches terminées à l'utilisateur :

typescript
console.log(`Nombre de tâches terminées : ${completedTasksCount()}`);
console.log(`Nombre de tâches terminées : ${completedTasksCount()}`);

Dans cet exemple, nous utilisons la fonction completedTasksCount comme une fonction pour obtenir la valeur actuelle du signal completedTasksCount et l'afficher dans la console.

Bien comprendre computed

Un calculé (computed) est une valeur dérivée qui est automatiquement mise à jour en fonction d'autres valeurs ou signaux. Pour expliquer cela à un débutant, prenons un exemple concret de la vie quotidienne.

Supposons que vous ayez une recette de cuisine pour préparer un gâteau. La recette indique que vous avez besoin de deux ingrédients : la farine et le sucre. Cependant, vous souhaitez également connaître la quantité totale de ces ingrédients nécessaires pour préparer plusieurs gâteaux.

Pour résoudre ce problème, vous pouvez utiliser un calculé pour calculer la quantité totale d'ingrédients en fonction du nombre de gâteaux que vous souhaitez préparer.

Imaginons que la quantité de farine nécessaire pour un gâteau soit de 200 grammes et la quantité de sucre soit de 100 grammes. Vous pouvez créer un calculé pour calculer la quantité totale de farine et de sucre en fonction du nombre de gâteaux.

Par exemple, si vous souhaitez préparer 3 gâteaux, le calculé peut être défini comme suit :

typescript
const nombreDeGateaux = 3;
const quantiteDeFarineParGateau = 200;
const quantiteDeSucreParGateau = 100;

const quantiteTotaleDeFarine = computed(() => nombreDeGateaux * quantiteDeFarineParGateau);
const quantiteTotaleDeSucre = computed(() => nombreDeGateaux * quantiteDeSucreParGateau);
const nombreDeGateaux = 3;
const quantiteDeFarineParGateau = 200;
const quantiteDeSucreParGateau = 100;

const quantiteTotaleDeFarine = computed(() => nombreDeGateaux * quantiteDeFarineParGateau);
const quantiteTotaleDeSucre = computed(() => nombreDeGateaux * quantiteDeSucreParGateau);

Dans cet exemple, le calculé quantiteTotaleDeFarine est défini en multipliant le nombre de gâteaux par la quantité de farine par gâteau. De même, le calculé quantiteTotaleDeSucre est défini en multipliant le nombre de gâteaux par la quantité de sucre par gâteau.

Maintenant, chaque fois que vous changez le nombre de gâteaux, les calculés se mettent automatiquement à jour pour refléter la nouvelle quantité totale de farine et de sucre nécessaires.

effet()

Voici le code complet d'une simple application de liste de tâches utilisant les signaux :

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

interface Task {
  id: number;
  description: string;
  isCompleted: boolean;
}

@Component({
  selector: 'app-todo-list',
  template: `
    <h2>Ma liste de tâches</h2>
    Tâche terminée: {{ isTaskCompleted() }}

    <ul>
      <li *ngFor="let task of tasks">
        {{ task.description }} 
        <button (click)="markTaskAsCompleted(task)">Terminée</button>
      </li>
    </ul>

    <p>Nombre de tâches terminées : {{ completedTasksCount() }}</p>
  `,
})
export class TodoListComponent {
  tasks: Task[] = [
    { id: 1, description: 'Faire les courses', isCompleted: false },
    { id: 2, description: 'Nettoyer la maison', isCompleted: false },
    { id: 3, description: 'Répondre aux e-mails', isCompleted: false },
  ];

  isTaskCompleted = signal(false);

  completedTasksCount = computed(() => {
    return this.tasks.filter(task => task.isCompleted).length;
  });

  constructor() {
    effect(() => {
      console.log('Nombre de tâches terminées :', this.completedTasksCount());
    });
  }

  markTaskAsCompleted(task: Task) {
    task.isCompleted = true;
    this.isTaskCompleted.set(true);
  }
}
import { Component, signal, computed, effect } from '@angular/core';

interface Task {
  id: number;
  description: string;
  isCompleted: boolean;
}

@Component({
  selector: 'app-todo-list',
  template: `
    <h2>Ma liste de tâches</h2>
    Tâche terminée: {{ isTaskCompleted() }}

    <ul>
      <li *ngFor="let task of tasks">
        {{ task.description }} 
        <button (click)="markTaskAsCompleted(task)">Terminée</button>
      </li>
    </ul>

    <p>Nombre de tâches terminées : {{ completedTasksCount() }}</p>
  `,
})
export class TodoListComponent {
  tasks: Task[] = [
    { id: 1, description: 'Faire les courses', isCompleted: false },
    { id: 2, description: 'Nettoyer la maison', isCompleted: false },
    { id: 3, description: 'Répondre aux e-mails', isCompleted: false },
  ];

  isTaskCompleted = signal(false);

  completedTasksCount = computed(() => {
    return this.tasks.filter(task => task.isCompleted).length;
  });

  constructor() {
    effect(() => {
      console.log('Nombre de tâches terminées :', this.completedTasksCount());
    });
  }

  markTaskAsCompleted(task: Task) {
    task.isCompleted = true;
    this.isTaskCompleted.set(true);
  }
}

La fonction effect est utilisée pour créer un effet. Elle prend en argument une fonction callback qui sera exécutée lorsque les signaux dépendants changent. Dans ce cas, la fonction callback est une fonction fléchée sans arguments.

Bien comprendre les effets

Imaginons que vous ayez une plante dans votre maison et que vous vouliez vous assurer qu'elle reçoit suffisamment d'eau. Pour atteindre cet objectif, vous décidez de mettre en place un effet simple.

Vous décidez de placer un détecteur d'humidité dans le pot de la plante. Lorsque le niveau d'humidité du sol est trop bas, cela signifie que la plante a besoin d'eau. Pour réagir à cette situation, vous avez prévu un effet qui est déclenché lorsque le niveau d'humidité est bas.

Cet effet pourrait consister en une action de verser de l'eau dans le pot de la plante. Lorsque le détecteur d'humidité détecte un niveau d'humidité insuffisant, il déclenche l'effet, c'est-à-dire l'action de verser de l'eau dans le pot.

Dans cet exemple, l'effet est une réaction à un événement spécifique, à savoir un niveau d'humidité insuffisant. Il permet de prendre des mesures pour résoudre le problème en fournissant de l'eau à la plante.

Dans le développement logiciel, un effet fonctionne de manière similaire. Il réagit à des événements ou à des changements spécifiques dans une application et déclenche une action correspondante. Cela peut inclure des actions telles que l'affichage d'un message d'erreur, l'envoi de données à un serveur, la mise à jour de l'interface utilisateur, etc.

Récapitulatif

ConceptDescription
SignalUn signal est une valeur réactive utilisée pour représenter un état ou une donnée changeante dans une application. Les signaux peuvent être créés à l'aide de la fonction signal et peuvent être mis à jour à l'aide de la méthode set. Les signaux peuvent être utilisés pour exprimer des dépendances et déclencher des effets lorsqu'ils sont mis à jour.
ComputedUn signal calculé est un signal dérivé qui est calculé automatiquement en fonction d'autres signaux. Il est créé à l'aide de la fonction computed et prend une fonction de calcul en argument. Les signaux calculés sont mis à jour automatiquement lorsque les signaux dont ils dépendent changent.
EffectUn effet est une fonction callback qui est exécutée lorsque des signaux spécifiques sont mis à jour. Les effets sont créés à l'aide de la fonction effect et permettent d'effectuer des actions supplémentaires en réponse à des changements de signaux. Les effets peuvent être utilisés pour les effets secondaires tels que les appels API ou les mises à jour de l'interface utilisateur.