Cours Angular · Module 6: Signals
La tour et le messager - les signals à côté des flux
Dans cette leçon9
Pendant tout ce module, tu as bâti des tours à signaux. Un signal répond à une seule question et il y répond sur-le-champ : combien y en a-t-il maintenant. Tu appelles level() et tu reçois un nombre dans la même milliseconde, sans attendre et sans demander la permission à personne. Voilà ce qu'est un état.
Il existe pourtant des nouvelles qui n'entrent pas dans cette question. La cloche de la grande porte de l'Académie sonne coup après coup toute la journée : ce n'est pas une valeur unique, c'est une suite de coups étalée dans le temps. Le messager envoyé au château voisin reviendra dans trois jours, ou ne reviendra pas du tout. L'éclaireur posté sur la route fait cinq rapports au cours de l'après-midi, puis se tait jusqu'au soir. Si tu voulais décrire ces choses-là avec une tour, il te faudrait poser la question "combien de coups de cloche en ce moment" et recevoir une réponse qui ne dit rien du fait que d'autres coups vont suivre.
Une suite de ce genre, tu en as déjà croisé une sur les routes du Tōkaidō. Quand tu lisais les paramètres d'un itinéraire par route.paramMap sans passer par l'instantané, tu ne recevais pas une valeur, mais un objet auquel il fallait s'abonner avec la méthode subscribe() pour qu'Angular appelle ta fonction à chaque nouveau message. Cet objet s'appelle Observable, en français un flux, et toute la bibliothèque d'outils qui travaille sur ces flux - RxJS - t'attend au module suivant, chez les éclaireurs ninja. Aujourd'hui, tu n'apprends pas RxJS. Aujourd'hui, tu apprends ce sans quoi les flux et les signals vivraient dans deux châteaux séparés : comment traduire les uns en les autres, et pourquoi choisir l'un plutôt que l'autre.
Une petite convention avant de commencer, car tu la verras dans chaque exemple. En Angular, le nom d'une variable qui contient un flux se termine par un dollar : timer$, query$, events$. C'est un accord entre humains et rien de plus, le compilateur n'y prête pas la moindre attention, mais grâce à lui on sait au premier coup d'oeil qu'il y a là-dedans une suite de valeurs et non un seul nombre.
Un pont bâti de tes propres mains
Partons de l'embarras, car c'est lui qui explique tout le reste de la leçon. Tu veux afficher dans un template le nombre de coups de cloche, et les coups arrivent en flux. Le template d'Angular ne sait pas lire un flux comme il lit un signal : il n'y a pas là de parenthèses qui renverraient quoi que ce soit. Tu dois donc bâtir un pont, c'est-à-dire t'abonner au flux et recopier chaque valeur qui arrive dans un signal, que le template, lui, comprend déjà.
Pour fabriquer le flux, j'utiliserai dans l'exemple la fonction interval de la bibliothèque RxJS. Tu lui passes un nombre de millisecondes et elle te rend un flux qui émet un nombre de plus à chaque intervalle : zéro, un, deux, trois. C'est tout ce que tu dois en savoir aujourd'hui. Le reste du code ci-dessous t'est connu : le décorateur @Component, le signal créé avec signal() et la méthode set() des relais précédents de ce module.
1import { Component, signal } from '@angular/core';
2import { interval } from 'rxjs';
3
4@Component({
5 selector: 'app-gate-bell',
6 standalone: true,
7 template: '<p>Coups de cloche : {{ ticks() }}</p>'
8})
9export class GateBellComponent {
10 ticks = signal(0);
11
12 constructor() {
13 interval(1000).subscribe(value => {
14 this.ticks.set(value);
15 });
16 }
17}Ce code fonctionne : chaque seconde le flux émet un nouveau nombre, l'abonnement le reçoit, set() l'inscrit dans le signal et le template affiche le coup suivant. Remarque bien ce que cette construction n'a pas changé. Le template lit ticks() exactement comme n'importe quel autre signal de ce module et ignore complètement que la valeur est arrivée de l'extérieur. Le prix à payer se cache ailleurs : tu as écrit trois choses à la main au lieu d'une. Le champ qui garde la valeur, l'abonnement, et - celle-là, on l'oublie le plus facilement - une dette à rembourser, car il convient de fermer l'abonnement à la destruction du composant, faute de quoi la cloche continue de sonner pour un composant qui n'est plus à l'écran. Le même pont recopié dans vingt composants, ce sont vingt occasions d'oublier une fois.
toSignal - le guetteur posté à la porte
À l'Académie, personne n'oblige le commandant à courir lui-même au-devant des rapports. Un guetteur se tient à la porte : il reçoit chaque rouleau apporté par un messager et le traduit aussitôt en fumée au-dessus de la tour. Le commandant ne regarde que la tour et n'a aucun besoin de savoir combien de messagers courent sur la route. Angular possède exactement ce guetteur, et il s'appelle toSignal().
La fonction toSignal() reçoit un flux et rend un signal qui affiche toujours la dernière valeur de ce flux. Le sens est unique, et un seul : du flux vers le signal, du messager vers la tour. Elle habite un paquet à part, @angular/core/rxjs-interop, et non le @angular/core que tu connais - Angular tient les passerelles vers RxJS à l'écart pour qu'une application qui n'utilise pas RxJS n'ait pas à l'embarquer. La ligne d'import se compose de quatre morceaux dans un ordre immuable : le mot import, l'accolade contenant le nom { toSignal }, le mot from, et enfin le nom du paquet entre apostrophes '@angular/core/rxjs-interop'.
Le second argument, facultatif, est un objet d'options. Aujourd'hui, une seule entrée t'y sera utile : initialValue, la valeur que le signal doit afficher tant que le flux n'a rien dit.
1import { Component } from '@angular/core';
2import { toSignal } from '@angular/core/rxjs-interop';
3import { interval } from 'rxjs';
4
5@Component({
6 selector: 'app-gate-bell',
7 standalone: true,
8 template: '<p>Coups de cloche : {{ ticks() }}</p>'
9})
10export class GateBellComponent {
11 private timer$ = interval(1000);
12
13 ticks = toSignal(this.timer$, { initialValue: 0 });
14}Trois lignes se sont réduites à une, et c'est tout le changement visible à l'oeil nu. Bien plus intéressant est ce qui n'a pas bougé. Le template est identique au signe près, car ticks reste un signal ordinaire que l'on lit avec des parenthèses. Le flux, lui, est resté un flux : timer$ ne sait rien des signals et tu peux encore le passer à qui tu veux. En revanche, les deux choses qu'il fallait surveiller ont disparu : l'abonnement, c'est toSignal() qui l'ouvre à présent, et c'est lui qui le referme tout seul quand le composant est détruit. C'est précisément pour cela que cet appel doit se trouver là où Angular sait de quelle destruction il s'agit - dans un champ de classe ou dans le constructeur, dans ce même contexte d'injection où, au module des guildes, tu appelais inject().
Retiens tout de suite une limite de plus, car elle surprend souvent. Le signal rendu par toSignal() est en lecture seule : il n'a ni set() ni update(). Je l'ai vérifié au compilateur - tenter d'appeler ticks.set(5) se termine par l'erreur "Property 'set' does not exist on type 'Signal<number>'" et l'application ne se construit pas. Ce n'est pas un caprice, c'est une conséquence : la valeur est dictée par le flux, si bien que si tu avais le droit d'y écrire quoi que ce soit par le côté, le prochain rapport du messager l'écraserait de toute façon. L'appel lui-même garde toujours la même forme, en quatre morceaux : le nom toSignal, la parenthèse ouvrante, le nom du flux timer$, la parenthèse fermante.
Quand le flux n'a encore rien dit
L'option initialValue n'est pas obligatoire, d'où la question naturelle : qu'affiche le signal si tu ne la donnes pas et que le messager vient tout juste de partir ? Ni zéro, ni une valeur vide inventée par Angular. Dans ce cas, toSignal() rend un signal dont le type est "la valeur ou bien undefined", et la première lecture donne justement undefined.
1private timer$ = interval(1000);
2
3ticks = toSignal(this.timer$);J'ai exécuté les deux variantes côte à côte et le résultat est sans ambiguïté : la version sans options renvoie undefined juste après sa création, tandis que la version avec { initialValue: 0 } renvoie zéro. Après deux coups de cloche, les deux affichent déjà la même chose, car à partir du premier rapport il n'y a plus la moindre différence entre elles. Le type, en revanche, a changé, et cela se voit dans tout le composant : sans valeur initiale, TypeScript te réclamera un traitement d'undefined à chaque endroit où tu iras chercher un champ du résultat.
Tu as trois issues possibles, et j'ai parmi elles ma préférée. La première, c'est initialValue. La deuxième, c'est de laisser undefined et de masquer l'affichage avec une condition @if dans le template. La troisième, { requireSync: true }, s'adresse aux flux qui émettent une valeur immédiatement à l'abonnement - le type est alors propre, mais au prix d'un risque : je l'ai essayée sur le flux interval, qui justement ne répond pas tout de suite, et Angular a levé l'erreur NG0601 en annonçant que le flux n'avait pas émis de valeur de façon synchrone. Mon conseil : pour les flux continus, comme la cloche ou les événements de la souris, donne une initialValue, car zéro coup est une vérité sur le monde. Pour une réponse unique venue du réseau, laisse undefined, car ce mot signifie honnêtement "les données ne sont pas encore là", alors qu'un tableau vide fabriqué de toutes pièces mentirait en prétendant que les données sont arrivées et qu'elles sont vides.
Un rapport venu de l'autre bout du monde
Le flux le plus fréquent dans une vraie application ne bat pas la seconde : il apporte une seule réponse du serveur, puis se tait. Pour parler au serveur, Angular fournit le service HttpClient, auquel un module entier est consacré plus loin, du côté des messagers. Aujourd'hui, une seule de ses propriétés te suffit : la méthode get() ne rend rien immédiatement. Elle rend un flux qui émettra une valeur unique lorsque la réponse arrivera, et qui s'achèvera là-dessus. Le service, tu le prends avec inject() du module des guildes, et entre les chevrons placés après get tu indiques le type des données attendues.
Mettons-nous d'abord d'accord sur ces données. L'interface User décrit un utilisateur : elle a un champ id de type number, pour pouvoir le distinguer des autres, et un champ name de type string. Le serveur rendra la liste complète, si bien que le type de la réponse est User[].
1import { Component, inject } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3import { toSignal } from '@angular/core/rxjs-interop';
4
5interface User {
6 id: number;
7 name: string;
8}
9
10@Component({
11 selector: 'app-users',
12 standalone: true,
13 template: `
14 @if (users()) {
15 @for (user of users(); track user.id) {
16 <p>{{ user.name }}</p>
17 }
18 }
19 `
20})
21export class UsersComponent {
22 private http = inject(HttpClient);
23
24 users = toSignal(this.http.get<User[]>('/api/users'));
25}Le composant entier tient maintenant en deux champs et pas une seule méthode. La condition @if (users()) n'est pas un ornement : elle est là précisément parce que je n'ai pas donné d'initialValue, si bien que jusqu'à la réponse users() renvoie undefined et que la boucle @for n'aurait rien à parcourir. La syntaxe @if et @for, avec son track obligatoire, tu la connais du module sur les composants, et track user.id dit à Angular à quoi reconnaître une même ligne d'un rafraîchissement à l'autre. Regarde maintenant ce que toSignal() n'a pas fait, car c'est ici que naissent le plus d'illusions. Il n'a pas envoyé la requête : c'est HttpClient qui s'en est chargé. Il ne l'a pas relancée après une erreur, il n'a pas patienté, il n'a pas mis le résultat en réserve pour plus tard et il ne sait pas redemander une seconde fois. Le guetteur de la porte se contente de traduire les rapports en fumée, et ce rapport-là, personne ne l'apportera deux fois.
Les quatre pas du pont
Arrêtons-nous un instant pour ranger le chemin entier dans l'ordre, car c'est une de ces suites qu'il vaut la peine de savoir réciter. D'abord naît le flux, par exemple interval(1000). Ensuite, toSignal() le transforme en signal. Ensuite, tu lis la valeur en appelant ce signal avec des parenthèses - timerSignal() si tu nommes le champ d'après le minuteur, ou ticks() dans notre cloche. Et pour finir, effect(), vu à une leçon précédente de ce module, réagit à chacun de ses changements, car il suit tout signal lu à l'intérieur de son corps.
Ajoutons ce dernier pas au composant de la cloche. L'effet tout entier tient en un appel dans le constructeur, exactement à l'endroit où tu posais tes effets aux leçons précédentes.
1constructor() {
2 effect(() => {
3 console.log('Coup numero', this.ticks());
4 });
5}À partir de cet instant, chaque coup de cloche part dans la console. Le plus important, c'est ce qui manque dans cet effet : pas un mot sur le flux, pas de subscribe(), pas d'interval, pas même un import de RxJS. L'effet voit un signal ordinaire et n'a pas la moindre idée qu'un messager court de l'autre côté du pont. C'est tout l'intérêt de cette passerelle : une fois le flux devenu signal, tout ce que tu as appris dans ce module - la lecture par parenthèses, les valeurs dérivées de computed(), les effets - fonctionne dessus sans le moindre changement.
toObservable - le messager envoyé depuis la tour
Le pont sert parfois dans l'autre sens, et ce n'est pas de l'art pour l'art. Imagine la fenêtre de recherche des guerriers dans les archives de l'Académie. L'élève tape lettre après lettre, et tu veux attendre trois cents millisecondes après le dernier caractère avant de partir vers le serveur, puis réessayer si la connexion tombe. Un signal ne fera pas cela. Un signal sait répondre à la question de l'instant présent ; il ne sait pas attendre, ni mesurer le temps, ni recommencer, car la notion de "plus tard" lui est étrangère. Les opérateurs de RxJS, eux, le savent, et tu en entendras parler chez les éclaireurs ninja. Mais pour pouvoir t'en servir, tu dois d'abord retransformer la tour en messager.
C'est le travail de toObservable(), la seconde fonction du même paquet @angular/core/rxjs-interop. Elle reçoit un signal et rend un flux qui émet les valeurs successives de ce signal. Dans le template de l'exemple, deux détails viennent du module sur les composants : $event désigne l'événement du navigateur transmis par la liaison (input), et $any(...) est un raccourci d'Angular qui demande au compilateur de templates de ne pas vérifier le type de cette expression, le temps d'atteindre la propriété value du champ de saisie.
1import { Component, signal } from '@angular/core';
2import { toObservable } from '@angular/core/rxjs-interop';
3
4@Component({
5 selector: 'app-search-box',
6 standalone: true,
7 template: '<input (input)="query.set($any($event.target).value)">'
8})
9export class SearchBoxComponent {
10 query = signal('');
11
12 query$ = toObservable(this.query);
13}Tu as maintenant deux poignées sur la même chose : query pour le template et query$ pour le monde des flux. Et rien n'a changé dans le signal lui-même - query reste inscriptible, tu le lis toujours avec des parenthèses et c'est toujours lui la source de vérité. Retiens bien ce couple de noms, car les confondre est l'erreur la plus fréquente sur ce sujet : toSignal() mène du flux au signal, toObservable() du signal au flux. Le compilateur est ici impitoyable, et c'est une bonne nouvelle. J'ai testé les deux emplois fautifs : passer un signal à toSignal() se solde par un message disant que WritableSignal ne correspond pas à un paramètre de type Observable, et passer un flux à toObservable() par un message disant que Observable ne correspond pas à un paramètre de type Signal. Aucune de ces deux fonctions, en revanche, ne sert à synchroniser des signals entre plusieurs composants - c'est le rôle d'un service partagé, vu au module des guildes - ni à déboguer quoi que ce soit.
Il y a une chose que je n'inventerai pas ici, parce qu'elle ne s'exécute pas hors d'une application complète : je n'ai pas fait tourner ce flux dans une console. Les types de la documentation indiquent en revanche que toObservable() fait passer les valeurs par un effet, et qu'il exige pour cette raison un contexte d'injection, exactement comme toSignal(). La conclusion pratique est la suivante : le flux rapporte une valeur stabilisée et non chaque écriture prise séparément - si tu règles le signal trois fois dans le même instant, l'autre rive verra l'état final.
La tour ou le messager - comment choisir
Puisque les deux mondes se laissent relier en une seule ligne, reste la question la plus importante : pourquoi aller chercher un flux, si les signals sont plus simples ? Maître Ango-san donne toujours le même conseil en la matière, et je te recommande cet ordre comme une procédure de décision toute prête.
Première question : est-ce une valeur synchrone et simple, une valeur dont on peut demander "combien en ce moment" ? Le niveau d'un guerrier, la ligne sélectionnée, le drapeau d'un panneau ouvert. Alors tu prends signal() et l'affaire est close. Deuxième question : cette valeur est-elle dérivée d'autres signals, autrement dit peut-on la calculer à partir d'eux ? Alors tu prends computed() et tu n'y penses plus jamais. Troisième question : est-ce un flux asynchrone, dans lequel le temps et l'ordre comptent - des réponses du serveur, des messages d'un WebSocket, une suite d'événements du clavier ? Ici commence le terrain de RxJS. Quatrième question, qui referme la boucle : as-tu déjà un flux tout prêt, et t'en faut-il un signal dans le template ? Alors tu poses toSignal() et tu rentres au pays des tours.
Vérifions cela sur quatre tâches concrètes, car c'est seulement ainsi que la règle se met en place. Garder un état simple de chargement et d'erreur, c'est un travail de signal : ce sont deux valeurs dont on demande "où en est-on maintenant", et le temps n'y intervient pas. Une valeur de compteur synchrone dans un composant, c'est également un signal, un cas d'école même : le nombre existe à cet instant et ne changera pas de lui-même. Un simple calcul dérivé de deux valeurs, c'est computed(), car Angular le recalculera seul après chaque changement de ses ingrédients. Mais un flux d'événements avec une attente après le dernier caractère et une reprise après erreur - c'est-à-dire notre fenêtre de recherche - voilà une tâche qu'un signal ne mènera pas à bien. Il y faut des outils qui manipulent le temps et l'ordre des événements, et seul RxJS en possède. Leurs noms, tu les découvriras au module suivant ; aujourd'hui, retiens le critère lui-même : le temps et l'ordre sont le terrain des flux, l'état d'ici et maintenant celui des signals.
linkedSignal - l'ardoise que l'on a le droit de corriger à la craie
Angular 19 a ajouté deux outils à l'arsenal, et tous deux résolvent des ennuis déjà apparus dans cette leçon. Voici le premier. Dans la salle du conseil, une ardoise porte le nom de l'escouade désignée pour le départ. Par défaut, c'est la première escouade de la liste, mais le commandant a le droit de la rayer et d'en inscrire une autre à la craie. Seulement, quand un nouveau registre des escouades arrive du château, l'ardoise doit être réécrite depuis zéro : l'ancien choix pourrait ne plus exister.
Aucun des outils que tu connais ne fait cela. Une valeur dérivée par computed() se recalcule toute seule, mais elle est en lecture seule et le commandant n'y corrigera rien - je l'ai vérifié au compilateur, appeler set() sur une telle valeur se termine par une erreur disant que cette propriété n'existe pas. Un signal ordinaire créé par signal() fait l'inverse : il autorise l'écriture, mais il n'apprendra jamais l'existence du nouveau registre et gardera le nom d'une escouade dissoute. Il te faut quelque chose entre les deux, et c'est exactement linkedSignal() : un signal inscriptible qui, en même temps, se réinitialise tout seul quand sa source change. Tu lui donnes une fonction qui calcule la valeur de départ, exactement comme pour computed().
1import { signal, linkedSignal } from '@angular/core';
2
3const squads = signal(['Hikari', 'Kaze', 'Yama']);
4
5const selected = linkedSignal(() => squads()[0]);J'ai exécuté ce code : la première lecture de selected() renvoie la chaîne "Hikari", c'est-à-dire la première escouade du registre. Pour l'instant, cela ressemble à une valeur dérivée ordinaire, et rien d'étonnant à cela, car à ce stade le comportement est identique. La différence n'apparaîtra qu'à l'écriture et au remplacement du registre.
1selected.set('Yama');
2
3squads.set(['Tora', 'Ryu']);J'ai vérifié les deux appels sur du code exécuté. Après le premier, selected() renvoie "Yama" - l'écriture a donc réussi, ce qui avec computed() n'aurait même pas compilé. Après le second, selected() renvoie "Tora", soit la première escouade du nouveau registre. Personne ne l'a réglé : le changement de la source a invalidé de lui-même la correction à la craie et l'ardoise a été réécrite. Et là encore, le plus important est ce qui n'a pas changé - le signal squads ignore l'existence de selected et n'a passé aucun accord avec lui, et après la réinitialisation selected reste pleinement inscriptible, si bien que le commandant peut aussitôt y inscrire "Ryu".
Trois autres descriptions de linkedSignal() sonnent vrai, et toutes les trois sont fausses. Ce n'est pas un signal constant dont la valeur ne pourrait pas être modifiée - tu viens de le régler deux fois, et les méthodes set et update sont bien présentes sur lui, ce que j'ai vérifié sur l'objet en cours d'exécution. Il ne relie pas non plus deux composants par un service partagé ; pour partager un état entre composants, c'est le service du module des guildes qui sert, tandis que linkedSignal() vit tranquillement dans un seul fichier et ne sait rien d'aucun composant. Et il ne convertit pas les flux en signals - il vient du paquet @angular/core, et non de la passerelle vers RxJS, et il n'a aucune idée de l'existence d'Observable. Pour changer un flux en signal, il y a toSignal(), et lui seul.
resource() - l'expédition partie chercher un rouleau
Le second outil d'Angular 19 revient sur une affaire laissée en suspens à la liste des utilisateurs. Ce pont-là fonctionnait tant que les données se chargeaient une seule fois. Mais que se passe-t-il si le composant affiche la fiche d'un seul guerrier, si le numéro du guerrier arrive de l'extérieur par l'input.required() de la leçon précédente, et si ce numéro change ? Le flux de http.get() a apporté son unique réponse et s'est tu pour toujours. Il faudrait bâtir un nouveau flux pour le nouveau numéro et abandonner le précédent à mi-chemin - cela se fait avec les opérateurs de RxJS dont tu entendras parler au module suivant, encore faut-il savoir comment.
Angular 19 propose une réponse pensée dès le départ pour les signals : la fonction resource(). Appelle-la une expédition partie chercher un rouleau dans une bibliothèque lointaine. Tu lui passes un objet à deux champs. Le champ request est une fonction qui dit pourquoi on part : tu y lis des signals, et Angular veille à ce que le changement de l'un d'eux relance l'expédition. Le champ loader est une fonction qui dit comment obtenir les données ; elle est asynchrone et reçoit en argument le request courant. À l'intérieur, j'utiliserai la fonction fetch du navigateur, et je transformerai la réponse en objet avec la méthode json(). La fiche du guerrier, dans notre code, c'est l'interface Samurai, avec ses champs id et name. Ci-dessous, tu ne vois que le champ de classe - le reste du composant, avec son id = input.required<number>(), ressemble à celui de la leçon précédente.
1samurai = resource({
2 request: () => this.id(),
3 loader: async ({ request: id }) => {
4 const response = await fetch(`/api/samurai/${id}`);
5 return response.json() as Promise<Samurai>;
6 }
7});L'écriture { request: id } dans l'en-tête de la fonction consiste à extraire le champ request de l'argument et à lui donner le nom plus court id. J'ai compilé tout ce code et il passe sans réserve. Je ne l'ai en revanche pas exécuté, car resource() a besoin d'une application Angular en marche - tenter de l'appeler dans un Node nu se termine par une erreur signalant l'absence d'un fournisseur interne. Plutôt que d'inventer des résultats, je te dis donc franchement ce qui se passe : quand this.id() change de valeur, Angular appelle loader une nouvelle fois pour le nouveau numéro et interrompt l'expédition précédente restée inachevée. Et rien n'a changé dans la manière de récupérer les données - à l'intérieur, il y a toujours le simple fetch que tu as écrit toi-même.
Le résultat de l'expédition se lit avec des signals, et c'est toute la récompense de cette écriture. Le champ value() rend les données lorsqu'elles sont là. Le champ error() rend l'erreur si l'expédition a échoué. Le champ isLoading() rend vrai ou faux, et c'est dans le template qu'il est le plus commode. Quant au champ status(), il annonce la phase de l'expédition entière - nous en dirons plus dans un instant.
1@if (samurai.isLoading()) {
2 <p>Messager en route...</p>
3} @else if (samurai.error()) {
4 <p>Aucun rapport</p>
5} @else {
6 <p>{{ samurai.value()?.name }}</p>
7}Ce template couvre les trois fins possibles de l'expédition et n'a besoin d'aucun champ auxiliaire dans la classe. Le point d'interrogation de l'écriture value()?.name est indispensable, car tant que les données ne sont pas arrivées, value() renvoie undefined - c'est la même prudence que toSignal() sans valeur initiale t'avait déjà imposée.
Reste la phase promise, c'est-à-dire status(). Elle annonce où en est l'expédition : si les données se chargent encore, si elles sont arrivées et que c'est un succès, ou si tout s'est terminé par une erreur. Il vaut la peine de savoir aussi ce qui ne s'y trouve pas : il n'existe aucune phase de suspension, aucun "paused". Une expédition partie chercher un rouleau ne se met pas en pause à mi-chemin pour attendre un signe - tout au plus peut-on la relancer avec la méthode reload() ou l'interrompre en même temps que le composant. J'ajoute honnêtement que dans Angular 19, tout resource() est marqué comme expérimental et que l'écriture exacte du nom de ces phases a été précisée d'une version à l'autre. Retiens donc le sens, et non les lettres : chargement, succès, erreur - et pas la moindre trace de pause. Les noms en vigueur dans ta version, tu les vérifieras toujours dans la documentation d'Angular.
Retiens une seule chose de cette leçon : la tour répond à la question de l'instant, le messager apporte des nouvelles étalées dans le temps, et toSignal() et toObservable() sont les deux seuls ponts entre eux - l'un de la route vers les remparts, l'autre des remparts vers la route.
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. À quoi sert la fonction toSignal() de @angular/core/rxjs-interop ?
2. Pour quelle tâche RxJS est-il mieux adapté que Signals ?
Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Tri horizontal
Ordonne, sur une seule ligne, l'import de la fonction toSignal depuis le package rxjs-interop.
- Éditeur de code
Complète le composant qui convertit un Observable HTTP en Signal.
- Tri vertical
Ordonne le processus qui connecte un flux RxJS à un signal dans un composant.
- Clique dans l'ordre
Ordonne l'appel qui convertit le flux observable timer$ en signal.
- Tri vertical
Ordonne le processus de décision pour choisir un outil de réactivité pour l'état de l'UI.