Programmation "reactive"

La programmation "reactive" est un pattern de programmation, appelé l'Observer qui donne une place centrale aux événements. L'application va réagir à ces derniers, d'où le nom de programmation "reactive". Il faut préciser que les événements sont asynchrones par nature, détail à ne pas oublier !  

Angular utilise le pattern "Observer" notamment pour la gestion des événements custom, la gestion des requêtes HTTP, la gestion des formulaires ...

Flux (ou observable) et Observer

Un flux est une séquence ordonnée d'événements qui peuvent aboutir sur 3 cas de figure :

  • la récupération de données,
  • la levée d'une exception,
  • la clôture du flux.

Dans le pattern de programmation "reactive" on s'abonne (subscribe) à un flux (observable) via un "listener" ou "observer" qui a pour mission de gérer les 3 cas de figure ci-dessus.

Les Observables sont souvent comparés aux promesses mais ils sont plus complets : ils permettent d'annuler une requête et de la relancer. On peut également "composer" un observable depuis d'autres observables ...

RxJS

Reactive Extensions Library for JavaScript est la bibliothèque js de programmation reactive la plus connue. Elle fournit un certain nombre de fonctions pour gérer les flux dont les plus utilisées sont :

  • fromEvent() pour créer un Observable qui émet des événements d'un type spécifique provenant de la cible d'événement donnée.
  • of() qui émet un observable souvent à partir d'un tableau d'objets
  • subscribe() pour souscrire à un observable (et donc créer un observer)
  • pipe() pour transformer le flux souvent avec map, filter ou retry qui permet de tenter plusieurs fois la connexion en cas d'échec
  • ...

Angular et RxJS

Exemple de code :

cards.subscribe({ 
  next: (card: CardModel) => this.card = card, 
  error: () => this.error = true, 
  complete: () => this.handleEnd() 
});

Ce code se lit de la manière suivante : cards est un observable auquel on souscrit.

  • En cas de réception d'une donnée de type "CardModel", c'est le premier callback qui sera appelé,
  • si une erreur est levée, c'est le deuxième callback qui sera appelé,
  • enfin si l'observable est terminé, c'est le dernier callback qui est appelé.

RxJS et HttpClient

Le cas classique est de créer un Observable à partir d'une requête Http. Pour cela, il faut intervenir dans 3 fichiers de votre application :

  1. le fichier app.config :

        import { ApplicationConfig } from '@angular/core';
    	import { provideHttpClient } from '@angular/common/http';
    
    	export const appConfig: ApplicationConfig = {
    	  providers: [provideHttpClient()]
        };
        
  2. Le service qui va fournir la requête qui renvoie un Observable : 
     

    import { Injectable } from '@angular/core';
    	import { HttpClient } from '@angular/common/http';
    	import { Observable } from 'rxjs';
    
    	import { CardModel } from './models/card.model';
    
    	@Injectable({ providedIn: 'root' })
    	export class CardService {
    	  constructor(private http: HttpClient) {}
    
    	  list(): Observable<Array<CardModel>> {
    	    const params = { status: 'ENABLE' };
                const baseUrl = 'https:myservice.com';
    	    return this.http.get<Array<CardModel>>(baseUrl + '/api/cards', { params });
    	  }
    	}
  3. Le component qui va invoquer la méthode qui permet de récupérer l'Observale et surtout d'y souscrire :
     

    import { Component } from '@angular/core';
    	import { NgFor } from '@angular/common';
    
    	import { CardModel } from '../models/card.model';
    	import { CardComponent } from '../card/card.component';
    	import { CardService } from '../card.service';
    
    	@Component({
    	  selector: 'co-cards',
    	  standalone: true,
    	  imports: [NgFor, CardComponent],
    	  templateUrl: './cards.component.html',
    	  styleUrls: ['./cards.component.css']
    	})
    	export class CardComponent {
    	  cards: Array<CardModel> = [];
    	  error = false;
    
    	  constructor(private cardService: CardService) {
    	    // Ici on ne gère que le cas favorable
    	    this.cardService.list().subscribe(cards=> (this.cards = cards));
    	    // Si on avait voulu gérer le cas défavorable :
    	    //this.cardsService.list().subscribe({ next: cards => (this.cards = cards), error: () => (this.error = true) });
    	  }
    	}