7
minutes
Mis à jour le
18/7/2024


Share this post

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.

#
Angular
#
Observables
#
Signals
Léo Merran
Tech Lead

Introduction

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.

Quand utiliser un Signal ?

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.

Quand utiliser un Observable ?

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.

Comment faisons nous la conversion ?

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.

Changer le type de votre Observable

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)

Abandonner les @Input

Admettons que vous avez dans votre composant un @Input et que vous voulez afficher ses propriétés :


Lier vos mises à jour de données à l’input

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.

Abandonner les @Output

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.


Simplifiez le Two Way Binding

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.

Je ne sais pas gérer la mise à jour d’une liste avec les Subject !

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 :

Conclusion

Maintenant, vous avez toutes les billes en main pour migrer une partie de vos Observable vers les Signal. Terminons par une synthèse :

  • Observable
    • Si vous souhaitez mettre de la logique asynchrone complexe avec de multiples événements, composition et transformations de données, les opérateurs RxJs vous permettront de faire presque tout ;
    • Si votre architecture est événementielle où différentes parties de votre application doivent réagir à des événements et propager des changements. RxJs fournit des outils puissants pour gérer les flux d'événements et les dépendances d'événements ;
  • Signal
    • Si vous souhaitez gérer des événements simples sans logique combinatoire poussée ;
    • Certaines nouvelles librairies étant basées sur les Signal comme SignalStory, il est plus logique de travailler avec pour ne pas introduire une librairie externe comme RxJs ;
Documentation