Utilisation des signaux dans une application Angular
Les signaux ont fait leur apparition dans Angular. Mais c’est quoi un signal au juste ? Comment on l’utilise ? C’est ce que nous allons voir.
Au sommaire :
- C’est quoi un signal ?
- À quoi ça sert ?
- Comment les utiliser ?
- Pour résumer
- Liens et exemple complet
C’est quoi un signal ?
Un signal, c’est un type permettant de définir des valeurs réactives. Si vous avez utilisé RxJs, vous avez utilisé des valeurs réactive de type “Observable”. Le principe est similaire, ces types représentent des valeurs qui peuvent changer au cours du temps. On peut également calculer d’autres valeurs réactives à partir des premières et seront automatiquement mises à jour.
Alors quelle différence avec RxJs ? La mise en oeuvre des signaux est bien plus simple car le code peut être écrit de façon plus impérative qu’avec RxJs (avec lequel on écrit du code réactif dans des “pipes”). Mais attention, les signaux ne remplacent pas complètement RxJs, ils viennent d’ailleurs avec des opérateurs très pratiques pour convertir un Observable en Signal et inversement.
À quoi ça sert ?
Les applications sont nombreuses.
Les signaux peuvent servir à calculer une valeur dériviée à partir d’un ou plusieurs autres signaux. Par exemple, si un signal contient une liste de Todo, un signal dérivé pourrait compter le nombre de Todo non terminées. Ainsi, si la liste initiale change, le compteur changera automatiquement.
L’état d’un composant ou d’une fonctionnalité donnée peut être définie par un ou plusieurs signaux et dérivés. Ainsi, lorsque l’état change, toutes les parties de l’application ayant besoin d’être informées sur cet état sont notifiées.
On peut également déclencher des “effets”, des actions à réaliser à chaque fois qu’une valeur d’un signal change. Par exemple afin d’envoyer une requête HTTP à chaque fois que la valeur d’un signal change.
Comment les utiliser ?
Premièrement, il faut Angular 16+. D’autres fonctionnalités dans Angular utilisant les signaux ont été (et seront encore) ajoutées dans les versions suivantes. Je vous recommande donc d’avoir la dernière version disponible.
Dans la suite de l’article, nous allons prendre l’exemple d’un Pokédex utilisant un signal donnant les Pokémon attrapés. Un exemple complet fonctionnel est disponible sur Stackblitz.
signal()
Nous souhaitons avoir un service Pokedex
possédant les Pokémons attrapés. Pour cela, nous pouvons déclarer une
variable et utiliser la fonction signal()
pour l’initialiser. Nous allons créer un attribut pokemons
pour le service
qui sera un Signal<Pokemon[]>
initialisé avec un tableau vide :
Nous pouvons maintenant avoir une méthode qui va définir la liste les Pokémons, appellons la setPokemons()
. On peut
utiliser la méthode set()
d’un signal pour définir sa valeur :
Lorsque le signal est mis à jour (ici avec set()
), tous les composants, services ou vues qui utilisent ce signal
seront également mis à jour.
On peut lire la valeur de pokemons
en l’utilisant comme une fonction :
En plus de la méthode set()
, les signaux ont également une méthode update()
qui prend en paramètre une fonction de
callback. Cette fonction prendra elle-même en paramètre la valeur actuelle du signal et il faut qu’elle retourne la nouvelle
valeur pour le signal. C’est très pratique dans notre cas, car on ne souhaite ajouter qu’un seul Pokémon à la fois dans
notre Pokédex :
computed()
Puisque nous avons un signal nous permettant de toujours avoir une liste de Pokémon à jour, il serait intéressant de pouvoir calculer tout un tas de valeurs dérivées qui resteraient également à jour à chaque fois que la liste change. Par exemple, on pourrait avoir une valeur contenant le nombre de Pokémons attrapés et une autre les triant dans l’ordre du Pokédex.
Pour ce faire, on peut utiliser la fonction computed()
. Cette fonction créé un nouveau signal qui dépendra d’un ou
plusieurs autres signaux. À chaque fois que ces signaux changeront, la valeur du signal “computed” sera recalculée.
computed()
prend en paramètre une fonction :
Note : nous récupérons bien la valeur du signal pokemons
en l’invoquant : this.pokedex.pokemons()
.
effect()
Un effet est une fonction qui se déclenche lorsqu’un ou plusieurs signaux changent. Il n’a pas pour but de changer
l’état de l’application (ce n’est donc pas comme computed()
). J’aime voir les effets comme un “effet secondaire” ou un
“effet de bord” : lorsqu’un signal change il faut effectuer une certaine opération comme logguer une information,
appeler une API, stocker quelque chose dans le local storage, etc.
Les effect()
sont asynchrones et ne se produisent pas directement après le changement d’un signal, ils sont déclenchés
lors du processus de changement de détection. Même si c’est techniquement possible, les effets ne devraient pas mettre
à jour un autre signal : si vous entrez dans ce cas de figure, vous pouvez probablement utiliser computed()
.
Dans notre exemple, nous allons ajouter un signal contenant un certains nombre de Pokéballs :
Nous allons ensuite pouvoir créer un effet qui va dépendre de ce nombre de Pokéballs : à chaque fois que ce nombre change, nous présentons au dresseur un nouveau Pokémon qu’il pourra attraper ou non. S’il l’attrape, le nombre de Pokéball va diminuer, déclenchant à nouveau cet effet et ainsi de suite.
Vous remarquerez que nous avons déclaré effect()
dans le constructeur. C’est parce qu’il nécessite un “contexte
d’injection”, on peut donc l’utiliser :
- dans un constructeur
- assigné à un attribut d’une directive ou d’un service
- en passant le service d’injection d’Angular en second paramètre de l’effet :
Pour résumer
Nous avons vu qu’un signal()
est une valeur réactive, changeante dans le temps. On peut modifier sa valeur avec
set()
ou update()
et récupérer sa valeur en l’utilisant comme une fonction.
Un computed()
est un signal dérivant d’un ou plusieurs autres. Il est recalculé automatiquement dès que l’un des
signaux le constituant change.
Un effect()
est une fonction “effet de bord” permettant d’exécuter des opérations supplémentaires lorsqu’un ou
plusieurs signaux changent, comme appeler une API, sauvegarder des informations ou mettre à jour la vue.
Liens et exemple complet
Vous pouvez retrouver l’exemple complet sur Stackblitz.
Voici également quelques liens vers des sources de documentation et de quoi aller plus loin avec les signaux :