Découvrez comment Angular 17 révolutionne la gestion de l'asynchronisme avec les Signals. Explorez les cas d'usage et les équivalences entre Observables et Signals pour faciliter la migration et optimiser vos applications.
Pour les adeptes d’Angular, la sortie des Signals était une feature très attendue. Depuis Angular 17, ils ont stabilisé leurs utilisations et de plus en plus de personnes, initialement réticentes, se voient converti à leur utilisation. Cela à pour effet de créer deux clans pour la gestion de l’asynchronisme dans les applications web : les Observables VS les Signals.
Mais ce serait se fourvoyer que de penser que les deux ne peuvent pas cohabiter. En réalité, les Signals ne sont pas la solution à prendre dans 100% des cas. Dans certaines situations, les Observables resteront la meilleure solution. Je vais donc faire au travers de cet article des recommandations sur les cas d’usages et des équivalences pour faciliter la migration de l’un à l’autre.
En pratique, les Signals sont plus simples à mettre en place et à utiliser. Ils seront donc privilégiés dans la plupart des situations (moins de code, meilleure lisibilité et plus de compréhension,…). Ils sont principalement utilisés dans l'interface utilisateur et leur valeur peut être entièrement modifiée via set
ou mise à jour sur la base d'une valeur précédente avec update
. Cette fonctionnalité est un gros plus. En effet, les Subjects permettaient d’injecter une valeur manuellement dans l’observable, mais elle ne pouvait pas dépendre de la valeur précédente. Avec les Signals, on peut mettre à jour en fonction de la valeur actuelle.
Pour comprendre, partons d’un cas simple :
let myValue = 0;
On va pouvoir la modifier et l’utiliser dans le template comme on le souhaite, mais vu qu’elle n’est pas réactive en l’état, la valeur ne sera pas mise à jour. C’est là que les Signals rentrent en jeu. Ils vont permettre de wrapper cette valeur pour notifier en cas de mise à jour. Ainsi, je peux faire :
mySignal = signal(1)
pour l’initialiser et le mettre à jour de la sorte :
Avec cette base, on peut remplacer une partie des Observables, mais il y a toujours une notion RxJs utile, à savoir le highOrderMapping (mergeMap, switchMap,…). Pour cela, il existe la méthode computed
. Elle permet de créer un Signal en le combinant avec d’autres :
Gardez en tête que cette combinaison est assez primaire et permet de couvrir des cas simples.
Il est tentant de laisser tomber les Observable pour quelque chose de plus lisible et récent. Cependant, ils sont moins puissants que des Observables car moins flexibles de par l’absence de RxJs. Même si cette librairie est souvent la cause de beaucoup de bugs si elle est mal utilisée, elle n’en reste pas moins très complète pour répondre à presque tous les besoins.
Le principal avantage comparé aux Signals est le chaînage de pipe
pour créer des Observables complexe (combineLatest, forkJoin, withLatestFrom,…), de pouvoir gérer du cache ou encore rejouer de la donnée.
Maintenant que vous avez compris qu’il ne faudra pas convertir totalement votre application, je vais vous montrer comment passer des Observables aux Signals de façon simple.
La première étape pour commencer à nettoyer votre code peut être de simplement changer le type de vos Observables avec l’opérateur toSignal
:
⚠️ Le toSignal
déclenche directement l’abonnement de l’Observable dont il dépend. La première valeur peut être undefined si on ne fait pas attention à ce point ! C’est pour cela qu’on peut préciser grâce à initialValue
la première valeur (comme un BehaviourSubject en quelque sorte)
Admettons que vous avez dans votre composant un @Input
et que vous voulez afficher ses propriétés :
Reprenons l’exemple d’avant où, à chaque changement de l’input, je veux savoir si le Pokémon est capturé. Et si je le capture, je veux mettre à jour cette donnée :
A la base, on avait une variable boolean
mise à jour grâce à l’Observable, maintenant on travaille directement avec le Signal.
Si vous souhaitez cascader une valeur de l’enfant au parent, vous pouvez utiliser l’Output. Le problème, c’est que l’implémentation est très verbeuse. Angular 17 débarque avec un nouveau type d’output, le model
.
Avec
l’exemple d’avant vous n’êtes pas convaincu de la plus-value et je comprends, car la taille est sensiblement la même. Mais qu’en est-il si vous souhaitez cascader une valeur du parent à l’enfant de l’enfant au parent ? Et bien le code sera EXACTEMENT le même et là, ça devient intéressant !
Changer un store PlainRxJs (Subject / Observable)Les Signals peuvent être comparés à des Subjects car on peut les modifier. Ils ont cependant plus de souplesse, car on écrase la valeur d’un Subject avec .next()
et on la met à jour dans un Signal avec .set()
ou .update()
. De plus, il est possible d’exposer des Signals en lecture seule pour les protéger. Sachant ça, la conversion est assez triviale :
Vous me direz qu’il n’y a pas beaucoup de différence à part l’annotation. Mais encore une fois, il est plus simple d’afficher directement la valeur d’un Signal que celle d’un Observable car elle ne nécessite pas de variable intermédiaire.
Pour ceux ayant manié les Subjects, vous savez qu’il est très compliqué de manier une liste, car encore une fois, on écrase la valeur ! On pourrait passer par une Array classique, mais on perd l’aspect réactif. Il est donc difficile de simplement la compléter. Grâce aux Signals, c’est maintenant possible :
ConclusionMaintenant, vous avez toutes les billes en main pour migrer une partie de vos Observable vers les Signal. Terminons par une synthèse :