Dans cette section, vous allez ajouter les fonctionnalités de persistence de données suivantes à l’aide du HttpClient d’Angular.

  • Le HeroService va récupérer les données de héros via des requêtes HTTP.

  • Les utilisateurs pourront ajouter, modifier et supprimer des héros, et sauvegarder ces changements via HTTP.

  • Les utilisateurs pourront rechercher des héros par nom.

1. Activer les services HTTP

HttpClient est le mécanisme d’Angular pour communiquer avec un serveur distant via HTTP.

Pour rendre HttpClient disponible partout dans l’application:

  • Ouvrez l' AppModule

  • Ajoutez HttpClientModule dans le tableau d’imports.

2. Simuler un serveur de données

Ce tutoriel simule la communication avec un serveur distant en utilisant le module In-memory Web API.

Après avoir installé le module, l’application émettra des requêtes et recevra des réponses via le HttpClient sans savoir que l'In-memory Web API intercepte ces requêtes, les appliquant à un store de données en mémoire, et retournant des réponses simulées.

Cela vous permettra de manipuler HttpClient et d’en comprendre les mécanismes sans devoir monter un serveur d’API.

Cela peut également s’avérer utile au début du développement d’un projet, lorsque l’API n’est pas implémentée, ou incomplète.

Installez le package depuis npm:

npm i angular-in-memory-web-api

Ajoutez l’entrée suivante au tableau d’imports d' AppModule:

HttpClientInMemoryWebApiModule.forRoot(
  InMemoryDataService, { dataEncapsulation: false }
)
Votre IDE devrait afficher une erreur sur InMemoryDataService. C’est normal, il s’agit d’une classe que nous allons implémenter dans un instant.

La méthode de configuration forRoot() prend en paramètre une classe InMemoryDataService qui permet d’amorcer la base de données in-memory.

Générez-la avec le CLI Angular:

  ng g s services/in-memory-data

Importez maintenant ce fichier dans AppModule pour résoudre l’erreur dans la configuration.

Remplacez le code généré dans ce nouveau service par le suivant:

import { Injectable } from '@angular/core';
import { InMemoryDbService } from 'angular-in-memory-web-api';

import { Hero } from '../models/hero.model';

@Injectable({
  providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
  createDb(): {heroes: Hero[]} {
    const heroes = [
      { id: 11, name: 'Anakin Skywalker' },
      { id: 12, name: 'Wilhuff Tarkin' },
      { id: 13, name: 'Chewbacca' },
      { id: 14, name: 'Han Solo' },
      { id: 15, name: 'Greedo' },
      { id: 16, name: 'Jabba Desilijic Tiure' },
      { id: 18, name: 'Wedge Antilles' },
      { id: 19, name: 'Jek Tono Porkins' },
      { id: 20, name: 'Yoda' },
      { id: 21, name: 'Palpatine' }
    ];
    return {heroes};
  }

  // Overrides the genId method to ensure that a hero always has an id.
  // If the heroes array is empty,
  // the method below returns the initial number (11).
  // if the heroes array is not empty, the method below returns the highest
  // hero id + 1.
  genId(heroes: Hero[]): number {
    return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11;
  }
}

Ce fichier va remplacer mock-heroes.ts. En revanche, ne le supprimez pas tout de suite, il vous servira encore pendant quelques étapes de ce tutoriel.

En situation projet, quand votre serveur est prêt, il suffit de détacher l' In-memory Web API, et les requêtes de l’application transiteront jusqu’au serveur distant.

3. Héros et HTTP

Ouvrez le HeroService, et injectez HttpClient dans un attribut privé http dans son constructeur, juste au-dessus de l’injection de messageService.

Vous allez utiliser régulièrement messageService.add(), il serait donc judicieux de la sortir dans une méthode privée log():

private log(message: string): void {
  this.messageService.add(`HeroService: ${message}`);
}

Définissez un attribut privé heroesUrl dans HeroService, qui prendra pour valeur ’api/heroes' `.

3.1. Récupérer les héros avec HttpClient

Actuellement, HeroService.getHeroes() utilise la méthode of() de RxJS pour retourner un tableau de héros mockés en tant qu' Observable<Hero[]>.

Modifiez la méthode pour qu’elle utilise HttpClient, selon la syntaxe suivante:

myMethod(): Observable<MyReturnType> {
  return this.http.get<MyReturnType>(this.myUrl);
}

Une fois le navigateur rafraîchi, les données de héros devraient effectivement se charger depuis le serveur de mock.

Vous avez remplacé of() par http.get et l’application continue à fonctionner sans aucune autre modification car les deux implémentations de getHeroes() renvoient un Observable<Hero[]>.

3.2. Les méthodes HTTP retournent une valeur

Toutes les méthodes de HttpClient retournent un Observable RxJS de quelque chose.

HTTP est un protocole de requête/réponse. Vous émettez une requête, et il retourne une réponse unique.

En général, un observable peut retourner plusieurs valeurs au fil du temps. Un observable de HttpClient émet toujours une valeur unique et quand il se termine, n’émettra plus jamais.

Cet appel particulier à HttpClient.get retourne un Observable<Hero[]>, littéralement "un tableau de héros observable". En pratique, il retournera un unique tableau de héros.

3.3. HttpClient.get retourne les données de la réponse

HttpClient.get retourne le body de la réponse comme un objet JSON non-typé par défaut. En appliquant un spécifieur de type optionnel, <Hero[]>, vous activez les capacités de TypeScript, ce qui réduit les erreurs à la compilation.

La forme des données JSON est déterminée par l’API du serveur distant. L’API de ce tutoriel retourne les données des héros comme un tableau.

D’autres APIs peuvent enfouir les données dont vous avez besoin dans un objet. Vous pourriez devoir "faire remonter" ces données en passant le résultat Observable à travers l’opérateur map de RxJS. Vous vous en servirez un peu plus loin dans le tutoriel.

3.4. Gestion d’erreurs

Les choses ne se passent jamais comme prévu, surtout quand vous récupérez des données d’un serveur distant. La méthode HeroService.getHeroes() devrait catcher les erreurs et en faire quelque chose d’approprié.

Pour catcher les erreurs, vous allez utiliser la méthode "pipe" sur l’observable retourne par http.get(), accompagnée de l’opérateur RxJS catchError().

Modifiez l’implémentation de getHeroes() comme suit:

getHeroes (): Observable<Hero[]> {
  return this.http.get<Hero[]>(this.heroesUrl).pipe(
    catchError(this.handleError<Hero[]>('getHeroes', []))
  );
}

L’opérateur catchError() intercepte un Observable qui a échoué. L’opérateur passe alors cette erreur à la méthode qui traite les erreurs.

La méthode handleError() suivante reporte l’erreur et renvoie ensuite un résultat inoffensif pour que l’application continue à fonctionner.

La méthode pipe() de RxJS peut s’appliquer sur tout Observable, en tout point de l’application, que ce soit dans un service, un composant ou dans tout autre composant Angular.
Elle peut exécuter plusieurs opérateurs RxJS à la suite, il suffit pour cela de les séparer par des virgules. On les écrit généralement sur des lignes séparées. Ils sont ensuite exécutés dans l’ordre d’écriture, du haut vers le bas.
Cela permet d’enchaîner plusieurs opérations sur un Observable, chaque pipe prenant en paramètre la valeur de retour du pipe précédent.

3.4.1. handleError

La méthode handleError() suivante va être partagée par de nombreuses méthodes de HeroService, elle est donc générique de manière à remplir leurs différents besoins.

Plutôt que de traiter l’erreur directement, elle retourne une fonction de gestion d’erreur à catchError qu’elle a préalablement configurée avec le nom de l’opération qui a échoué et une valeur de retour inoffensive:

private handleError<T>(operation = 'operation', result?: T): (error: any) => Observable<T> {
    return (error: any): Observable<T> => {

      // TODO: send the error to remote logging infrastructure
      console.error(error); // log to console instead

      // TODO: better job of transforming error for user consumption
      this.log(`${operation} failed: ${error.message}`);

      // Let the app keep running by returning an empty result.
      return of(result as T);
    };
  }

Après avoir reporté l’erreur dans la console, le handler construit un message plus "user-friendly" et retourne une valeur inoffensive pour que l’application continue à fonctionner.

Parce que chaque méthode du service retourne un type d' Observable différent, handleError() prend un paramètre le type pour qu’elle puisse retourne une valeur inoffensive du type attendu par l’application.

3.5. Faire un tap dans l'Observable

Les méthodes du HeroService vont tap dans le flux de valeurs observables et renvoyer un message (via log() dans la zone de messages en bas de la page).

Elles vont pour ce faire utiliser l’opérateur tap de RxJS, qui regarde les valeurs observables, fait quelque chose avec, et les renvoie telles qu’il les a reçues. tap ne touche pas les valeurs en elles-mêmes.

Ajoutez un opérateur tap au-dessus de catchError, et appelez-y la méthode log() avec le message "fetched heroes" en paramètre.

3.6. Récupérer le héros par id

La majorité des APIs proposent une requête de get by id sous la forme baseURL/id.

Ici, la base URL est heroesURL que nous avons défini plus tôt dans le tutoriel (api/heroes), et id est le numéro du héros que vous souhaitez retrouver. Par exemple, api/heroes/13.

Modifiez la méthode getHero() du service selon les règles suivantes:

  1. Créez une constante url qui respecte le chemin évoqué plus haut, à partir de l’attribut heroesUrl et du paramètre id.

  2. Remplacez le retour mocké via of() par un appel à http.get sur l’url que vous venez d’écrire, et typez-le pour respecter la signature getHero().

  3. Dans un opérateur tap, appelez log() de manière à afficher le message suivant (pour l’id 13): "fetched hero id=13".

  4. Dans un opérateur catchError, appelez handleError() avec le bon type et le message suivant: `getHero id=${id}`

4. Mettre à jour les héros

Modifiez le nom d’un héros dans la vue détail. Lorsque vous tapez, le nom du héros se met à jour dans le titre en haut de la page. Mais quand vous cliquez sur le bouton "go back", les changements sont perdus.

Si vous voulez persister les changements, vous devez les envoyer au serveur.

A la fin du template du détail de héros, ajoutez un bouton intitulé "save" et bindez son clic sur une nouvelle méthode save().

Dans la classe du composant, implémentez la méthode save() selon ces règles:

  1. Appelez la méthode (pas encore implémentée) heroService.updateHero(), en lui passant l’attribut hero du composant en paramètre.

  2. Effectuez un subscribe sur updateHero(), et appelez-y la méthode goBack().

4.1. Ajouter HeroService.updateHero()

La structure de la méthode updateHeroes() est similaire à celle de getHeroes(), mais utilise la méthode http.put().

updateHero (hero: Hero): Observable<any> {
  return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
    tap(_ => this.log(`updated hero id=${hero.id}`)),
    catchError(this.handleError<any>('updateHero'))
  );
}

La méthode HttpClient.put() prend 3 paramètres:

  • l’URL

  • les données à mettre à jour (le héros modifié dans notre cas)

  • des options

L’URL est inchangée. L’API web des héros sait quel héros modifier en regardant l' id du héros.

L’API des héros attend un header HTTP spécifique dans les requêtes de sauvegarde. Ce header est dans la constante httpOptions définie dans le HeroService, que vous pouvez ajouter de suite entre la liste des imports et l’annotation @Injectable:

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

Une fois le navigateur rafraîchi, modifiez le nom d’un héros et sauvegardez. Vous devriez automatiquement revenir à la liste (ou au dashboard, selon la page précédente), et constater que la modification a été conservée.

5. Ajouter un nouveau héros

Pour ajouter un héros, cette application n’a besoin que de son nom. Vous pouvez utiliser un élément <input> accompagné d’un bouton add.

Dans le template du HeroesComponent, ajoutez le code ci-après juste sous le titre:

<div>
  <label>Hero name:
    <input #heroName />
  </label>
  <!-- (click) passe la valeur de l'input à add() puis vide l'input -->
  <button (click)="add(heroName.value); heroName.value=''">
    add
  </button>
</div>

Dans la classe, ajoutez la méthode add() suivante:

add(name: string): void {
  name = name.trim();
  if (!name) { return; }
  this.heroService.addHero({ name } as Hero).subscribe(hero => {
    this.heroes = this.heroes ? [...this.heroes, hero] : [hero];
  });
}

Quand le nom donné n’est pas vide ou uniquement composé d’espaces, la méthode crée un objet Hero-like à partir du nom (il ne lui manque que l’id), et le passe à la méthode addHero() du service.

Quand addHero sauvegarde avec succès, la callback du subscribe reçoit le nouveau héros et le pousse dans la liste des héros pour l’afficher.

Il ne reste donc plus qu’à implémenter addHero() dans le HeroService.

5.1. Ajouter HeroService.addHero()

Implémentez la méthode addHero() selon les consignes suivantes:

  1. La méthode prend en paramètre un Hero et retourne un Observable<Hero>.

  2. La méthode retourne le résultat de la méthode http.post(), qui prend les 3 mêmes paramètres que `http.put().

  3. En cas de succès, tap logue le message "added hero w/ id=${newHero.id}", newHero étant le paramètre de tap.

  4. En cas d’échec, catchError appelle handleError avec le message 'addHero'.

6. Supprimer un héros

Chaque héros dans la liste devrait avoir un bouton 'delete'.

Ajoutez ce bouton au template de HeroesComponent, sous l’élément <a> dans <li>. Il prend la class delete, affiche la lettre x et son clic est bindé sur la méthode delete(hero).

Remplacez le CSS du HeroesComponent par celui-ci pour ajouter les classes nécessaires pour le bouton de suppression:

/* HeroesComponent's private CSS styles */
.heroes {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}
.heroes li {
  position: relative;
  cursor: pointer;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  height: 1.6em;
  border-radius: 4px;
}

.heroes li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
}

.heroes a {
  color: #333;
  text-decoration: none;
  position: relative;
  display: block;
  width: 250px;
}

.heroes a:hover {
  color:#607D8B;
}

.heroes .badge {
  display: inline-block;
  font-size: small;
  color: white;
  padding: 0.8em 0.7em 0 0.7em;
  background-color:#405061;
  line-height: 1em;
  position: relative;
  left: -1px;
  top: -4px;
  height: 1.8em;
  min-width: 16px;
  text-align: right;
  margin-right: .8em;
  border-radius: 4px 0 0 4px;
}

button {
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
  font-family: Arial;
}

button:hover {
  background-color: #cfd8dc;
}

button.delete {
  position: relative;
  left: 194px;
  top: -32px;
  background-color: gray !important;
  color: white;
}

Ajoutez ensuite la méthode delete() dans la classe du composant:

delete(hero: Hero): void {
  this.heroes = this.heroes?.filter(h => h !== hero);
  this.heroService.deleteHero(hero).subscribe();
}

Même si le composant délègue la suppression du héros au HeroService, il reste responsable de la mise à jour de sa propre liste de héros. La méthode delete() du composant supprime immédiatement le "héros à supprimer" de la liste, anticipant ainsi que l’appel du HeroService sur le serveur sera un succès.

Le composant n’a vraiment rien à faire avec l' Observable retourné par heroService.delete(), mais il doit tout de même y subscribe.

Si vous oubliez de subscribe(), le service n’enverra pas la requête de suppression au serveur ! C’est une règle, un Observable ne fait rien tant que rien n’y a subscribe !
Vous pouvez le constater simplement en retirant le subscribe(), en cliquant sur "Dashboard" puis sur "Heroes". Vous verrez à nouveau la liste des héros inchangée (la suppression n’ayant pas réellement eu lieu).

6.1. Ajouter HeroService.deleteHero()

Ajoutez la méthode deleteHero():

deleteHero (hero: Hero | number): Observable<Hero> {
  const id = typeof hero === 'number' ? hero : hero.id;
  const url = `${this.heroesUrl}/${id}`;

  return this.http.delete<Hero>(url, httpOptions).pipe(
    tap(_ => this.log(`deleted hero id=${id}`)),
    catchError(this.handleError<Hero>('deleteHero'))
  );
}

Notez que:

  • cette méthode appelle HttpClient.delete.

  • l’URL contient l’id du héros, afin de savoir quel héros supprimer.

  • vous n’envoyez pas de données, contrairement à put et post (puisque seul l’id est nécessaire, et qu’il est dans l’URL).

  • vous envoyez tout de même les httpOptions.

7. Rechercher par nom

Dans ce dernier exercice, vous allez apprendre à chaîner les opérateurs d' Observable pour minimiser le nombre d’appels HTTP similaires et consommer la bande passante de manière économique.

Vous allez ajouter une feature de recherche de héros sur le dashboard. Quand l’utilisateur va taper un nom dans le champ de recherche, vous récupérerez au fur et à mesure les héros correspondant à ce nom. Votre but est d’émettre juste le nombre nécessaire de requêtes HTTP.

7.1. HeroService.searchHeroes

Commencez par ajouter une méthode searchHeroes au HeroService.

/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
  if (!term.trim()) {
    // if not search term, return empty hero array.
    return of([]);
  }
  const params = new HttpParams()
    .set('name', term);
  return this.http.get<Hero[]>(`${this.heroesUrl}`, {params}).pipe(
    tap(_ => this.log(`found heroes matching "${term}"`)),
    catchError(this.handleError<Hero[]>('searchHeroes', []))
  );
}

La méthode retourne immédiatement un tableau vide si le champ de recherche est vide. Le reste ressemble à getHeroes(). La seule différence significative est la constante params qui set un paramètre name avec la valeur de term.

Vous auriez également pu passer ce paramètre directement dans l’URL (`${this.heroesUrl}/?name=${term}`), mais cette méthode est moins lisible à mesure que les paramètres se multiplient, et elle atteindra rapidement ses limites dans le cas où des paramètres seraient optionnels, ce qui aurait pour effet d’envoyer des paramètres sans valeur, et qui n’est pas très propre.

7.2. Ajouter la recherche au dashboard

Ouvrez le template du DashboardComponent et ajoutez un nouvel élément <sw-hero-search> en bas du template.

Comme on pourrait s’y attendre, cet ajout va casser l’application, puisque vous n’avez pas encore créé le HeroSearchComponent.

7.3. Créer le HeroSearchComponent

Utilisez le CLI Angular pour générer un composant hero-search.

Remplacez le template de ce nouveau composant par celui-ci:

<div id="search-component">
  <h4>Hero Search</h4>

  <input #searchBox id="search-box" (input)="search(searchBox.value)" />

  <ul class="search-result">
    <li *ngFor="let hero of heroes$ | async" >
      <a routerLink="/heroes/{{hero.id}}">
        {{hero.name}}
      </a>
    </li>
  </ul>
</div>

Quand l’utilisateur tape dans le champ de recherche, un binding sur l’évènement input appelle la méthode search() du composant avec la nouvelle valeur de recherche.

Ajoutez ensuite ce script CSS dans la feuille de style du composant:

.search-result li {
  border-bottom: 1px solid gray;
  border-left: 1px solid gray;
  border-right: 1px solid gray;
  width: 195px;
  height: 16px;
  padding: 5px;
  background-color: white;
  cursor: pointer;
  list-style-type: none;
}

.search-result li:hover {
  background-color: #607D8B;
}

.search-result li a {
  color: #888;
  display: block;
  text-decoration: none;
}

.search-result li a:hover {
  color: white;
}
.search-result li a:active {
  color: white;
}
#search-box {
  width: 200px;
  height: 20px;
}


ul.search-result {
  margin-top: 0;
  padding-left: 0;
}

7.4. AsyncPipe

Comme attendu, *ngFor répète les héros.

Regardez attentivement et vous verrez que *ngFor itère sur une liste appelée heroes$, et non heroes.

<li *ngFor="let hero of heroes$ | async" >

Le $ est une convention de nommage qui indique que heroes$ est un Observable, et non un tableau.

*ngFor ne peut rien faire avec un Observable. Mais il y a également un caractère pipe (|) suivi d' async, qui identifie l' AsyncPipe d’Angular.

L' AsyncPipe souscrit (subscribe) à un Observable automatiquement afin que vous n’ayez pas à le faire dans la classe du composant.

7.5. Réparer la classe HeroSearchComponent

Nous devons maintenant implémenter les différents éléments de la classe du composant pour qu’il puisse fonctionner.

  1. Créez un attribut heroes$ de type Observable<Hero[]>.

  2. Créez un attribut privé searchTerms, et assignez-lui la valeur new Subject<string>();. Nous reviendrons juste après sur la notion de Subject.

  3. Créez une méthode search(), qui prend en paramètre un term de type string, et qui exécute l’instruction suivante: this.searchTerms.next(term);

  4. Dans ngOnInit(), affectez à l’attribut heroes$ l’attribut this.searchTerms.

  5. Ajoutez un pipe sur this.searchTerms dans l’instruction que vous venez d’écrire, et ajoutez-y successivement les opérateurs debounceTime(300), distinctUntilChanged(), et switchMap((term: string) ⇒ this.heroService.searchHeroes(term)).

7.6. Le Subject RxJS searchTerms

L’attribut searchTerms est déclaré en tant que Subject RxJS.

Un Subject est à la fois une source de valeurs observables et un Observable lui-même. Vous pouvez subscribe à un Subject comme vous le feriez sur un Observable.

Vous pouvez également pousser des valeurs dans cet Observable en appelant sa méthode next(value), ce que vous avez fait dans la méthode search().

La méthode search() est appelée via un binding sur l’évènement input du champ de recherche.

A chaque fois que l’utilisateur tape dans le champ, le binding appelle search() avec la valeur du champ, le "terme de recherche". L’attribut searchTerms devient un Observable qui émet un flux régulier de termes de recherche.

7.7. Chaîner les opérateurs RxJS

Passer un nouveau terme de recherche directement à searchHeroes() après chaque appui sur une touche créerait un nombre excessif de requêtes HTTP, ce qui utiliserait inutilement les ressources du serveur et consommerait inutilement de la bande passante.

A la place, la méthode ngOnInit() pipe les observables de searchTerms à travers une séquence d’opérateurs RxJS qui réduit le nombre d’appels à searchHeroes(), ne renvoyant finalement qu’un observable de résultats de héros bien timé.

Cette séquence devrait ressembler à ceci:

this.heroes$ = this.searchTerms.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap((term: string) => this.heroService.searchHeroes(term)),
);
  • debounceTime(300) attend que l’utilisateur arrête de taper dans le champ de rechercher pendant 300ms avant de laisser passer la dernière string. Vous ne ferez donc jamais d’appels plus fréquemment que toutes les 300ms.

  • distinctUntilChanged() s’assure que la requête n’est envoyée que si le texte du champ a changé.

  • switchMap() appelle le service de recherche pour chaque terme de recherche qui est passe à travers debounce et distinctUntilChanged. Il annule les observables des recherches précédentes, retournant uniquement le dernier observable du service de recherche.

Avec l’opérateur switchMap, chaque clé qui a passé debounce et distinctUntilChanged peut déclencher un appel à HttpClient.get(). Même avec une pause de 300ms entre les requêtes, vous pourriez avoir plusieurs requêtes HTTP en cours et elles pourraient ne pas répondre dans l’ordre où elles ont été envoyées.
switchMap préserve l’ordre original des requêtes en ne renvoyant que l’observable de l’appel HTTP le plus récent. Les résultats des appels précédents sont annulés.
Notez qu’annuler un précédent Observable de searchHeroes() ne met pas fin à la requête HTTP concernée. Les résulats sont simplement ignorés avant d’atteindre le code de l’application.

N’oubliez pas que la classe du composant ne subscribe pas à l'observable heroes$. C’est le job de l' AsyncPipe dans le template.

Une fois le navigateur rafraîchi, vous devriez pouvoir tester la recherche.

Et voilà, c’est terminé !

Voici un résumé de ce que vous avez accompli dans cette section:

  • Vous avez ajouté les dépendances nécessaires pour utiliser HTTP dans l’application.

  • Vous avez refactoré HeroService pour charger les héros depuis une API.

  • Vous avez étendu HeroService pour supporter les méthodes post(), put() et delete().

  • Vous avez mis à jour les composants pour autoriser l’ajout, la modification et la suppression de héros.

  • Vous avez configuré une in-memory web API.

  • Vous avez appris à utiliser les observables.

Ce tutoriel est maintenant terminé, vous pouvez donc passer à la suite avec le TP Pokédex. Si vous avez des questions, ou que certaines notions ne sont pas claires, c’est le moment de me solliciter :)