Où en sont les Angular signals avec la version 18.2 ?

Pourquoi les signaux ?

Pour développer une interface utilisateur (UI) complexe, les développeurs d'applications JavaScript doivent stocker, calculer et synchroniser l'état vers la couche de vue de l'application de manière efficace.

Les UI impliquent souvent plus que la simple gestion de valeurs simples, mais souvent le rendu d'un état calculé dépendant d'un arbre complexe d'autres valeurs ou d'états également calculés.

L'objectif des signaux est de fournir une infrastructure pour gérer cet état d'application afin que les développeurs puissent se concentrer sur la logique métier plutôt que sur ces détails répétitifs.

Les signaux sont utilisés dans la programmation réactive pour éliminer le besoin de gérer les mises à jour dans les applications, en offrant un modèle de programmation déclaratif basé sur les changements d'état.

En d'autre terme, les signaux permettent d'améliorer les performances et de simplifier la réactivité.

Voici l'ensemble des objectifs des signaux exprimés par l'équipe d'Angular :

  • Modèle clair et unifié pour le flux de données dans une application.
  • Support intégré pour l'état dérivé déclaratif.
  • Synchronisation des parties de l'UI qui nécessitent une mise à jour, avec une granularité au niveau des composants individuels.
  • Interopérabilité avec des bibliothèques réactives telles que RxJS.
  • Meilleure gestion pour éviter les erreurs courantes de performance de la détection des changements et les erreurs telles que ExpressionChangedAfterItHasBeenChecked.
  • Pouvoir créer des applications entièrement sans zone (zoneless), éliminant les problématiques de zone.js.
  • Simplification de nombreux concepts du framework, tels que les requêtes et les hooks de cycle de vie.

Qu'est-ce qu'un signal ?

Les signaux sont des valeurs réactives qui permettent de notifier des consommateurs lorsque leur valeur change.

Les signaux peuvent contenir n'importe quelle valeur, des primitives aux structures de données complexes.

On lit la valeur d'un signal en appelant sa fonction getter, ce qui permet à Angular de suivre où le signal est utilisé.

Les signaux peuvent être soit modifiables, soit en lecture seule.

Les signaux modifiables

Les signaux modifiables fournissent une API pour mettre à jour directement leurs valeurs.

Les signaux modifiables ont le type WritableSignal.

Vous créez des signaux modifiables en appelant la fonction signal avec la valeur initiale du signal :

Pour changer la valeur d'un signal modifiable, on peut utiliser soit .set() soit .update() pour calculer une nouvelle valeur à partir de la précédente :

Les signaux calculés

Les signaux calculés sont des signaux en lecture seule qui dérivent leur valeur d'autres signaux.

Vous définissez des signaux calculés en utilisant la fonction computed et en spécifiant une dérivation :

Le signal doubleCount dépend du signal count. Chaque fois que count est mis à jour, Angular sait que doubleCount doit également être mis à jour.

Les signaux calculés sont évalués paresseusement et mémorisés.

La fonction de dérivation de doubleCount ne s'exécute pour calculer sa valeur que lorsque que vous lisez doubleCount.

La valeur calculée est ensuite mise en cache, et si vous relisez doubleCount, elle renverra la valeur mise en cache sans recalculer. Si vous changez ensuite count, Angular sait que la valeur mise en cache de doubleCount n'est plus valide, et la prochaine fois que vous lirez doubleCount, sa nouvelle valeur sera calculée.

Les effets

Les signaux sont utiles car ils notifient les consommateurs intéressés lorsqu'ils changent.

Un effet est une opération qui s'exécute chaque fois qu'une ou plusieurs valeurs de signal changent. Vous pouvez créer un effet avec la fonction effect :

Les effets s'exécutent toujours au moins une fois.

Lorsqu'un effet s'exécute, il enregistre comme dépendances tous les signaux lus.

Chaque fois que l'une de ces valeurs change, l'effet s'exécute à nouveau.

Les effets s'exécutent de manière asynchrone durant le processus de détection des changements.

Cas d'utilisation des effets

Les effets sont rarement nécessaires dans la plupart du code des applications, mais peuvent être utiles dans des circonstances spécifiques :

  • Journalisation des données affichées et de leurs changements, soit pour l'analyse, soit comme outil de débogage.
  • Maintien de la synchronisation des données avec window.localStorage.
  • Ajout de comportements DOM personnalisés qui ne peuvent pas être exprimés avec la syntaxe de template.
  • Réalisation de rendus personnalisés sur un <canvas>, une bibliothèque de graphiques ou une autre bibliothèque UI tierce.

Par défaut, vous ne pouvez créer un effect() que dans un contexte d'injection (où vous avez accès à la fonction inject). La manière la plus simple de satisfaire cette exigence est d'appeler effect dans un constructeur de composant, de directive ou de service.

Les signal inputs, model inputs et signal queries

Les signal inputs

Les signal inputs permettent de lier des valeurs provenant de composants parents.

Ces valeurs, exposées sous forme de signal, peuvent changer durant le cycle de vie du composant.

Les signal inputs sont une alternative réactive à @Input(), offrant plusieurs avantages :

  • meilleure sécurité de type : les inputs requis n'ont pas besoin de valeurs initiales et les transformations sont automatiquement vérifiées.
  • marquage automatique des composants OnPush comme dirty lors de l'utilisation dans les templates.
  • dérivation facile des valeurs des inputs avec computed.
  • surveillance plus simple et locale des inputs avec effect au lieu de ngOnChanges ou de setters.

Angular propose deux variantes d'inputs :

  • inputs optionnels : par défaut, les inputs sont optionnels, sauf si input.required est utilisé. une valeur initiale explicite peut être spécifiée, sinon Angular utilisera undefined implicitement.
  • inputs requis : ces inputs ont toujours une valeur du type donné et sont déclarés avec la fonction input.required.

Les signal inputs sont des signaux en lecture seule. Pour accéder à la valeur courante de l'input dans un template, il suffit d'appeler le signal de l'input.

Comme pour les signaux, il est possible de dériver des valeurs à partir des inputs en utilisant computed.

Les models inputs

Les model inputs sont un type spécial d'input qui permettent à un composant de propager de nouvelles valeurs vers un autre composant.

Les deux types d'input permettent de lier une valeur à la propriété. cependant, les model inputs permettent d'écrire des valeurs dans la propriété.

Quand un composant écrit une nouvelle valeur dans un model input, Angular propage cette nouvelle valeur vers le composant qui lie une valeur à cet input. C'est ce qu'on appelle la liaison bidirectionnelle (comme pour ngModel).

Dans cet exemple, CustomCheckbox peut écrire des valeurs dans son checked model input, qui se propagent ensuite vers le signal isAdmin dans UserProfile. cette liaison maintient les valeurs de checked et isAdmin synchronisées.

Les différences entre model() et input() à retenir sont donc :

  • model() définit à la fois un input et une sortie pour supporter les liaisons bidirectionnelles.
  • ModelSignal est un WritableSignal dont la valeur peut être changée de n'importe où avec les méthodes set et update, ce qui émet une sortie. InputSignal est en lecture seule et ne peut être changé que via le template.

Utilisez les model inputs dans les composants destinés à modifier une valeur en fonction de l'interaction de l'utilisateur : par exemple un sélecteur de date.

les signal queries permettent à un composant ou une directive de trouver des éléments enfants et de lire les valeurs de leurs injecteurs. elles sont souvent utilisées pour récupérer des références à des composants, directives, éléments du DOM, et plus encore. il existe deux catégories de requêtes : les requêtes de vue et les requêtes de contenu.

Les signal queries

Les signal queries permettent à un composant ou une directive de trouver des éléments enfants et de lire les valeurs de leurs injecteurs. Elles sont souvent utilisées pour récupérer des références à des composants, directives, éléments du DOM, etc.

Les signal queries offrent plusieurs avantages par rapport aux requêtes basées sur des décorateurs (@ContentChild, @ContentChildren, @ViewChild, @ViewChildren) :

  • synchronisation plus prévisible : vous pouvez accéder aux résultats des requêtes dès qu'ils sont disponibles.
  • API simplifiée : toutes les requêtes retournent un signal, et les requêtes avec plusieurs résultats vous permettent de travailler avec un tableau standard.
  • meilleure sécurité de type : moins de cas où les résultats des requêtes incluent undefined.
  • inférence de type plus précise : TypeScript peut inférer des types plus précis avec un prédicat de type ou une option read explicite.
  • mises à jour paresseuses : Angular met à jour les résultats des requêtes basées sur des signaux de manière paresseuse ; le framework ne fait aucun travail à moins que votre code ne lise explicitement les résultats des requêtes.

Il existe deux catégories de requêtes : les requêtes de vue et les requêtes de contenu.

Les requêtes de vue récupèrent les résultats des éléments du propre template (vue) du composant :

viewChildren permet de déclarer une requête pour plusieurs résultats.

Les requêtes de contenu récupèrent les résultats des éléments dans le contenu du composant, c'est-à-dire les éléments imbriqués à l'intérieur de la balise du composant dans le template où il est utilisé.

contentChild permet de déclarer une requête ciblant un seul résultat.

contentChildren permet de déclarer une requête pour plusieurs résultats.

Si une requête enfant (viewChild ou contentChild) ne trouve pas de résultat, sa valeur est undefined. pour éviter cela, vous pouvez marquer les requêtes enfants comme requises.

L'adoption des signaux par Wiz

An image showing the Angular and the Wiz frameworks logos connected with a line in the middle.

Wiz est un framework interne (privé) de Google utilisé par des applications de grande envergure comme YouTube.

Il a récemment adopté la bibliothèque de signaux d'Angular pour alimenter son interface utilisateur. Cette adoption marque une transition significative dans la manière dont les mises à jour de l'interface utilisateur (UI) sont gérées, avec un impact notable sur la performance des applications.

Les signaux d'Angular permettent à Wiz d'effectuer des mises à jour UI plus fines et granulaires. Au lieu de mettre à jour l'ensemble de l'interface utilisateur en réponse à chaque changement d'état, les signaux d'Angular permettent de cibler précisément les parties de l'UI qui nécessitent une mise à jour. Cela optimise l'utilisation des ressources et améliore la réactivité de l'application.

Il est prévu que l'adoption des signaux d'Angular améliorera également les performances d'autres grandes applications telles que Google Search et GMail.

Notez que Wiz et Angular vont fusionner et qu'il ne restera qu'Angular, mais c'est le sujet d'un autre article.

L'ajout à JavaScript des signaux

Une proposition de standardisation des signaux en JavaScript a été lancée.

Le design actuel est basé sur les contributions des auteurs et mainteneurs de frameworks tels qu'Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz, et d'autres.

Vous pouvez la suivre ici :

GitHub - tc39/proposal-signals: A proposal to add signals to JavaScript.
A proposal to add signals to JavaScript. Contribute to tc39/proposal-signals development by creating an account on GitHub.

Signaux vs RxJS ?

Cette explication vient du créateur de RxJS, Ben Lesh, qui fournit un aperçu succinct des différences entre les observables et les signaux.

Observables

Les observables sont utilisés pour gérer les événements. Ils sont principalement conçus pour coordonner des événements ou pour traiter des valeurs asynchrones (des événements) de manière éphémère. L'idée principale est de chaîner des fonctions pour pousser des valeurs. Voici quelques points clés :

  1. Gestion des événements : les observables excellent dans la gestion des événements, en écoutant et en réagissant aux changements de manière continue.
  2. Valeurs asynchrones : ils sont parfaits pour les scénarios où les données arrivent de manière asynchrone, comme les requêtes HTTP, les clics de souris, etc.
  3. Éphémère : les valeurs dans les observables ne sont pas conservées en mémoire de manière permanente. Elles sont traitées et ensuite éliminées, ce qui les rend idéales pour les flux de données continues et temporaires.

Signaux

Les signaux, en revanche, sont utilisés pour gérer l'état. Ils sont particulièrement efficaces pour l'état que vous allez utiliser pour mettre à jour une vue. Voici les points importants :

  1. Gestion de l'état : les signaux sont optimisés pour suivre l'état des données et refléter ces changements dans l'interface utilisateur.
  2. Mémoire : les valeurs stockées dans les signaux sont retenues en mémoire. Cela signifie que tout ce que vous mettez dans un signal reste en mémoire jusqu'à ce qu'il soit explicitement supprimé ou modifié.
  3. Signaux calculés : les signaux calculés sont essentiellement des mémorisations avec un graphe de dépendances. Cela signifie qu'ils calculent et mettent en cache les valeurs en fonction des dépendances, mais ils ne sont pas gratuits en termes de coût de performance. Il faut les utiliser en gardant à l'esprit leur coût et leur utilité.

En résumé, les observables sont mieux adaptés pour la gestion des événements et des valeurs asynchrones, tandis que les signaux sont optimisés pour la gestion de l'état et la mise à jour des vues.

Consultez notre formation Angular qui recevra une grande mise à jour pour la version 19 !