4. Factoriser nos requêtes dans un service

Par rapport à notre code précédent, nous avons encore un problème, c’est que nous avons dupliqué du code. La requête permettant de récupérer un pokémon depuis l’API en fonction de son identifiant est décrite deux fois, une fois dans le composant PokemonDetail et une autre fois dans le composant PokemonEdit.

En tant que futur bon développeur, cela doit nous alerter. Il est urgent de factoriser ce code dans un fichier à part ! 😉

Je vous propose donc de créer une nouvelle classe PokemonService, qui sera chargé d’effectuer les requêtes réseaux vers notre API Rest. Nous allons placer cette classe dans un dossier services, afin de garder la structure de notre projet organisée.

Créer donc un nouveau fichier pokemon-service.ts, dans un dossier services :

import Pokemon from "../models/pokemon";

export default class PokemonService {

  static getPokemons(): Promise<Pokemon[]> {
    return fetch('http://localhost:3001/pokemons')
      .then(response => response.json());
  }

  static getPokemon(id: number): Promise<Pokemon|null> {
    return fetch(`http://localhost:3001/pokemons/${id}`)
      .then(response => response.json())
      .then(data => this.isEmpty(data) ? null : data);
  }

  static isEmpty(data: Object): boolean {
    return Object.keys(data).length === 0;
  }
}

Dans cette classe, nous avons factoriser nos deux requêtes permettant d’accéder aux pokémons de l’API Rest.

D’abord, nous ajoutons la requête permettant de récupérer tous les pokémons dans la méthode getPokemons, à la ligne 5. Cette méthode retourne le résultat de la méthode fetch, qui est une promesse contenant un tableau de pokémons. C’est pour cela que l’on ajoute le type Promise<Pokemon[]> à notre méthode.

Ensuite on implémente la requête qui permet de récupérer un seul pokémon via son identifiant, à la ligne 10. Cette méthode renvoie une promesse contenant soit un pokémon, soit la valeur null si l’identifiant passé en paramètre ne correspond à aucun pokémon du côté de l’API Rest. Pour symboliser ce type particulier « un pokemon OU la valeur null« , on utilise le symbole « | » en TypeScript, afin de définir un type qui peut avoir les deux valeurs. Cela donne donc « Pokemon|null« , tout simplement.

Pour savoir s’il faut retourner un pokémon ou la valeur null, on regarde si la réponse de notre API Rest est un pokémon ou un objet vide. On se fait aider pour cela par la méthode isEmpty, définit à la ligne 16. On retourne enfin la bonne valeur à la ligne 13, c’est-à-dire soit un pokémon, soit la valeur null.

Et pourquoi toutes les méthodes de ce service sont préfixés par le mot-clé « static » ? 🤔

Bien vu ! D’abord, vous devez savoir que le terme méthode statique n’est pas propre à TypeScript mais à la programmation objet en générale. Pour faire simple, cela veut dire que votre méthode n’est pas rattaché aux instances de votre classe, mais à la classe elle même. Pour ne pas me perdre dans des explications trop pénibles, je vous propose de raisonner par l’absurde.

Sans le mot-clé static, il faudrait utiliser le code suivant à chaque fois que l’on voudrait utiliser une méthode de notre service :

// Méthode traditionnelle
const service = new PokemonService(); // 1. On récupère notre service.
const pokemonsPromise = service.getPokemons() // 2. On récupère la promesse depuis notre service.

pokemonsPromise.then(pokemons => console.info(pokemons)); // 3. On affiche la liste de tous les pokémons.

Avec l’usage de méthodes statiques, nous n’avons pas à instancier notre service, et nous pouvons utiliser la classe PokemonService directement :

// Méthode statique
PokemonsService.getPokemons().then(pokemons => console.info(pokemons)); // 1. Affiche la liste de tous les pokémons

Comme vous pouvez le voir, ce code me paraît beaucoup plus adapter à ce que nous voulons faire, à savoir récupérer nos pokémons depuis l’API Rest, sans passer par des étapes intermédiaires inutiles.

Maintenant, il nous reste à brancher nos composants sur ce nouveau service révolutionnaire. 😎

On commence par s’échauffer avec PokemonList, dans le fichier pokemon-list.tsx :

// Les importations.
import PokemonService from '../services/pokemon-service';
 
const PokemonList: FunctionComponent = () => {
  ...
 
  useEffect(() => {
    // GET Request
    PokemonService.getPokemons().then(pokemons => setPokemons(pokemons));
  }, []);
 
  return (...);
}
 
export default PokemonList;

À la ligne 9, on utilise notre service pour récupérer la liste des pokémons depuis l’API Rest, et ensuite on met à jour l’état de notre composant avec les pokémons fraîchement reçus.

Le principe est identique pour le composant pokemon-detail.tsx, mis à part que nous appelons la méthode getPokemon(id) cette fois :

// Les importations.
import PokemonService from '../services/pokemon-service';
 
const PokemonDetail: FunctionComponent = () => {
  ...
 
  useEffect(() => {
    // GET Request
    PokemonService.getPokemon(+match.params.id).then(pokemon => setPokemon(pokemon));
  }, []);
 
  return (...);
}
 
export default PokemonDetail;

À la ligne 9, vous remarquer que l’on préfixe l’identifiant du pokémon match.params.id par le symbole « + ». Là encore, c’est une petite astuce de syntaxe que propose TypeScript, qui permet de convertir une chaine de caractère en un nombre. En effet, le routeur de React nous transmet l’identifiant d’un pokémon sous la forme d’une chaîne de caractères, et la méthode getPokemon attend un nombre en paramètre. On effectue donc cette conversion à la volée, grâce à ce symbole « + ».

On effectue ensuite la même opération dans le composant pokemon-edit.tsx :

// Les importations.
import PokemonService from '../services/pokemon-service';
 
const PokemonEdit: FunctionComponent = () => {
  ...
 
  useEffect(() => {
    // GET Request
    PokemonService.getPokemon(+match.params.id).then(pokemon => setPokemon(pokemon));
  }, []);
 
  return (...);
}
 
export default PokemonEdit;

Il n’y a rien de nouveau dans ce composant, nous appelons notre service de la même façon que précédemment.

Nous avons centraliser nos appels à l’API Rest dans un service dédié, que nous pourrons utiliser et faire évoluer lors de nos futurs développements. Il ne reste plus qu’à passer à la suite !

Dans votre éditeur de code, vous pourriez avoir un message d’avertissement qui s’affiche sous les méthodes de notre service de pokémon : “this maybe converted to async fonction..; » Vous pouvez ignorer ce message, il s’agit plus d’une recommandation que d’un avertissement, nous invitant à utiliser le nouveau mot clé async de JavaScript pour nos fonctions asynchrones. Mais nous n’en avons absolument pas besoin pour développer notre application.