
Réactivité asynchrone avec "resource()"
Angular a récemment introduit un nouveau type réactif : les signaux. La plupart d’entre eux (signal()
, computed()
,
input()
, etc.) sont synchrones. Dans ce cas, comment traiter les données qui proviennent de sources asynchrones,
comme les requêtes HTTP ?
Depuis Angular 19, nous pouvons utiliser resource()
pour cela. Une “Resource” permet de charger une donnée de façon
asynchrone et de la recharger automatiquement lorsqu’un signal change. Elle permet également d’avoir des signaux
pratiques sur l’état du chargement.
Comment l’utiliser ?
Pour définir une “Resource”, il faut utiliser la fonction resource()
. Cette fonction prend en paramètre un objet
d’options ayant deux attributs obligatoires :
request
: cet attribut est similaire àcomputed()
, c’est-à-dire une fonction utilisant un signal et dont la valeur de retour dépend des changements de ce signal.loader
: cet attribut prend pour valeur une fonction qui charge la donnée. Cette fonction est rappelée à chaque fois querequest
change.
Dans cet article, nous prendrons pour exemple une petite application permettant de charger les données d’un Pokémon
choisi par l’utilisateur depuis une API (via une requête HTTP). La mise en place de resource()
pour une telle
application ressemblerait au code ci-dessous (un lien vers l’exemple complet est disponible en fin d’article) :
selectedPokemon = signal(1);
query = resource({
request: () => ({ pokedexId: this.selectedPokemon() }),
loader: ({ request }) =>
lastValueFrom(
this.#httpClient.get<Pokemon>(
`https://tyradex.vercel.app/api/v1/pokemon/${request.pokedexId}`
)
),
});
Dans le code ci-dessus, lorsque le signal selectedPokemon
est initialisé ou à chaque fois que sa valeur change, alors
request
est changé et donc le loader
est rappelé. Ce loader renvoie une promesse contenant la valeur asynchrone,
ici il s’agit d’une requête HTTP avec HttpClient
.
J’insiste sur le fait qu’il faut que la valeur du signal change. Si on définit plusieurs fois la même valeur pour ce
signal, le loader
ne sera pas rappelé. Ce qui est parfait pour limiter les requêtes HTTP envoyées à un serveur par
exemple !
Le loader
Dans l’exemple précédent, nous avons défini le loader
comme une fonction prenant en paramètre un objet et renvoyant
la ressource. Angular va passer en paramètre au loader un objet contenant les attributs suivants :
request
: nous l’avons vu dans l’exemple, il s’agit de la valeur renvoyée par l’optionrequest
.previous
: un objet de typeResourceStatus
nous donnant des informations sur la valeur précédente de la Resource (et son statut de chargement). Nous verrons plus en détails ce qu’il contient plus loin.abortSignal
: un moyen d’annuler la récupération de la donnée, voir AbortSignal sur MDN.
Forcer le rechargement des données
Puisqu’on ne peut pas re-définir un signal à une valeur identique, comment recharger la Resource si on a besoin de la
récupérer à nouveau ? Eh bien on peut utiliser resource.reload()
:
selectedPokemon = signal(1);
query = resource({
// ...
});
// ...
query.reload();
Statut de chargement de la Resource
Resource
met à disposition plusieurs signaux permettant de récupérer le statut du chargement asynchrone :
isLoading()
: comme le nom l’indique, seratrue
si un chargement est en cours.error()
: contiendra l’erreur rencontrée la plus récente, ouundefined
si aucune erreur ne s’est produite.hasValue()
etvalue()
permettent de savoir si une valeur est disponible et de la récupérer.status()
: contient un objetResourceStatus
permettant d’avoir le statut du chargement (Idle
,Error
,Loading
,Reloading
,Resolved
etLocal
).
On peut donc utiliser cet état dans notre template (ou composants et services) :
@if (query.isLoading()) {
<div>Loading Pokémon...</div>
} @else if (query.hasValue()) {
@let pokemon = query.value()!;
<div>
<h2>{{ pokemon.name.fr }}</h2>
<div><img [src]="pokemon.sprites.regular" /></div>
</div>
}
Ce que resource()
ne fait pas
Cet article est le premier d’une série, nous verrons qu’Angular prévoit de nous mettre à disposition des outils
supplémentaires et nous verrons également quelques modules externes qui permettent d’aller plus loin. Tel quel,
resource()
ne permet pas de :
- Gérer l’expiration des données après un temps défini (“stale time”).
- Gérer le hors-ligne (“offline”).
- Options de caching des données.
- Préchargement des données (par exemple, pré-charger la page suivante).
Malgré ces quelques options manquantes, resource()
est conçu pour être une brique de base à partir de laquelle des
fonctions plus spécifiques seront construites (par exemple httpResource
à partir de 19.2). Il est également possible
de développer soi-même ces outils supplémentaires en passant à la Resource les signaux nécessaires (mais je pense que
la team Angular ou d’autres modules verront le jour pour cela).
Liens et exemple complet
Vous pouvez retrouver l’exemple complet sur Stackblitz.
Voici également quelques liens vers des sources de documentation :