Réactivité asynchrone avec "resource()"

Réactivité asynchrone avec "resource()"

Loïc Boutter (lazybobcat)
Loïc Boutter (lazybobcat)

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 que request 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) :

Définir une Resource
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’option request.
  • previous : un objet de type ResourceStatus 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() :

Forcer le rechargement
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, sera true si un chargement est en cours.
  • error() : contiendra l’erreur rencontrée la plus récente, ou undefined si aucune erreur ne s’est produite.
  • hasValue() et value() permettent de savoir si une valeur est disponible et de la récupérer.
  • status() : contient un objet ResourceStatus permettant d’avoir le statut du chargement (Idle, Error, Loading, Reloading, Resolved et Local).

On peut donc utiliser cet état dans notre template (ou composants et services) :

Affichage dans le template
@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 :

Retour aux articles