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 :

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

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.