Vidéo de référence : https://www.youtube.com/watch?v=TrDqaABq-UY&ab_channel=DevoxxFR
RxJS est une bibliothèque de programmation réactive utilisant des "observables", pour faciliter la composition de code asynchrone ou basé sur les "callbacks".
ReactiveX a été conçu pour gérer de manière idéale les séquences d'événements.
Les concepts essentiels de RxJS qui résolvent la gestion des événements asynchrones sont :
Observable
Un observablee représente l'idée d'une collection invocable de valeurs ou d'événements futurs. On peut voir un observable comme un flux de données qui vont arriver séquentiellement de façon asynchrone ou pas. Un observable émet trois types de notifications :
- next : émission d'une nouvelle valeur,
- error : erreur qui termine l'observable,
- complete : termine l'observable sans erreur.
Exemple simple
Mise en place du projet:
mkdir my-app && cd my-app npm init -y
Installation des packages
npm install --save rxjs npm install --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin json-server
Paramétrage de webpack
Création d'un fichier webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "development", devServer: { static: "./dist", }, plugins: [ new HtmlWebpackPlugin({ title: "My App", template: "src/index.html", }), ], };
Mise en place de json-server :
Creation d'un fichier db.json
à la racine du projet
{ "data": [ { "id": 1, "item": "Item 1" }, { "id": 2, "item": "Item 2" }, { "id": 3, "item": "Item 3" } ] }
Modification du fichier package.json :
{ "name": "my-app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "server": "json-server --watch db.json", "start": "webpack serve" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "html-webpack-plugin": "^5.5.3", "install": "^0.13.0", "json-server": "^0.17.4", "npm": "^10.2.4", "rxjs": "^7.8.1", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" } }
Création d'un fichier src/index.html comprenant un div d'id app
Création du fichier src/index.js
import { from } from "rxjs"; const getData = () => { return from( fetch("http://localhost:3000/data").then((response) => response.json()) ); }; const appDiv = document.getElementById("app"); getData().subscribe((data) => { data.forEach((item) => { const div = document.createElement("div"); div.textContent = item.item; appDiv.appendChild(div); }); });
Lancement de json server
npm run server
Lancement de l'application
npm start
Autre exemple de création d'un observable directement en instanciant la classe Observable :
import { Observable } from 'rxjs'; const myObservable = new Observable((observer) => { let count = 0; const timer = setInterval(() => { observer.next(count); count++; if (count === 10) { observer.complete(); clearInterval(timer); } }, 1000); });
On peut créer des observables depuis :
- un événement ,
- la fonction fetch,
- un tableau,
- une chaîne de caractères,
- une promesse,
- ...
Exemple de création d'un observable depuis la fonction fetch (extrait d'une classe) :
import { fromFetch } from "rxjs/fetch"; import { switchMap, catchError, shareReplay } from "rxjs/operators"; import { Observable, of } from "rxjs"; export class Data { ... buildRequestObservale() { return fromFetch("http://localhost:3000/books").pipe( // switchMap : la seule réponse qui nous intéresse est la dernière switchMap((response) => { if (response.ok) { console.log(`Reponse ok`); return response.json() } else return of({ error: true, message: `Error ${response.status}` }) }), // shareReplay va renvoyer le dernier résultat sans que la requête soit à nouveau exécutée shareReplay(1), catchError((err) => { console.error(err) return of({ error: true, message: err.message }) }) ); } }
Subscription
Une subscription représente l'exécution d'un Observable. Elle permet également d'annuler l'exécution via la méthode unsubscribe.
Exemple de souscription à "myObservable" précédemment défini :
import { Observable } from 'rxjs'; const subscription = myObservable.subscribe({ next: (v) => console.log(v), error: (e) => console.error(e), complete: () => { console.info("completed"); subscription.unsubscribe(); }, });
Operators
Les opérateurs sont des fonctions qui permettent un style de programmation fonctionnel permettant de gérer des collections avec des opérateurs telles que map, filter, concat, reduce, etc.
Les opérateurs RxJs sont des fonctions pures qui revoient toujours un nouvel observable sans modifier celui d'origine.
Les opérateurs se "branchent" sur un observable via la méthode "pipe()" à laquelle on passe des fonctions qui sont les opérateurs. Il existe plus d'une centaine d'opérateurs.
Voici quelques exemples d'opérateurs classiques :
- filter : renvoie un observable qui émet des valeurs qui satisfont la condition fournie
- every : renvoie un observable à partir d'un test (vrai ou faux) sur tous les éléments.
- map : renvoie un observable créé à partir de la projection de chaque valeur de la source
- reduce : renvoie un observable aprés avoir réduit les valeurs observables de la source à une valeur unique émise lorsque la source est terminée.
- shareReplay : Permet de rejouer les valeurs d'un abonnement. Par exemple shareReplay(1) renvoie le dernier résultat de l'observable.
- ...
Exemple de l'utilisation de l'operateur filter :
import { filter } from "rxjs/operators"; const even = myObservable.pipe(filter((num:number) => num % 2 === 0));
Observer
Observer est une collection de rappels qui sait écouter les valeurs fournies par l'Observable.
Subject
Equivaut à un EventEmitter et constitue le seul moyen de multidiffuser une valeur ou un événement vers plusieurs observateurs.
Schedulers
Ce sont des répartiteurs centralisés pour contrôler la concurrence, nous permettant de coordonner le moment où le calcul a lieu, par exemple. setTimeout ou requestAnimationFrame ou autres.
Exercice auto-complete
A partir de l'exemple donné en minute 44 de la conférence de Thierry Chatel et avec l'aide de l'API REST https://restcountries.com/v3.1/name/xxx, mettez en place un observable à deux dimensions (input + fetch) pour faire appel à l'API dans les meilleures conditions.
Vous uliserez les opérateurs suivants : map, filter, debounceTime, distinctUntilChange.