Retourner à : Angular et RxJS – Mini Introduction
Introduction
Vous l’avez beaucoup demandé, donc voici une nouvelle série de cours dédiées à RxJS, et plus précisément à l’utilisation d’RxJS dans vos projets Angular. Vous n’utilisez pas Angular, ne vous enfuiez pas tout de suite, car RxJS n’est pas n’est pas limité à Angular et vous pourrez utiliser les notions qu’on abordera dans votre Framework préféré.
Pour ce premier chapitre on va:
- Expliquer ce qu’est RxJS.
- On verra en détail les concepts d’Observable, Observer et de Subscription.
- Pour finir on illustrera le tout avec deux examples.
Définitions
Commençons tout de suite par expliquer ce qu’est RxJS. RxJS est l’acronyme de “Reactive Extensions for JavaScript”, ce qui se traduit par “extension réactive pour JavaScript”. Cette librairie permet de traiter des données asynchrones de manière réactive.
Pour comprendre cela il faut comprendre deux choses, premièrement ce que sont les évènements asynchrones, et deuxièmement ce qu’on entend par la réactivité. Les évènements asynchrones sont des évènements qui peuvent survenir à tout moment, comme par example un click sur un bouton, une réponse à une requête HTTP, ou dans notre example des des frappes de lettres dans notre bar de recherche.

La réactivité quant à elle, consiste à observer des événements et à y réagir en cas de changement. Le but étant de créer des interface plus dynamique et un code plus facile à comprendre et à maintenir.
Observable

Maintenant qu’est ce que cela signifie concrètement dans le contexte de RxJS, comment peut-on observer des évènement et être notifier de leur changements. Et bien pour cela RxJS nous mets des Observables à disposition, un observable c’est quoi ? C’est un objet qui est un wrapper au tour d’évènemtns asynchrones, donc par example un wrapper autour d’un appel HTTP, des évenements clicks ou des frappes au clavier, et auquel un Subscriber pourra s’abonner en faisant appel à la méthode subscribe de l’observable afin d’être notifier de tout changement emis par cette observable.
Observer

Pour s’abonner à un Observable, le Subscriber devra passer un observer en paramètre à la méthode subscribe de l’observable. Cet observer est composé d’une collection d’une ou plusieurs des méthodes suivantes: la méthode next qui prendra une instance d’un évenement en paramètre, la méthode error, qui sera apellé en cas d’erreur emise par l’Observable, et la méthode complete qui est utilisé par l’Observable afin d’indiquer qu’il a terminé sa tache et n’émettra plus de valeurs.
Subscription

La méthode « subscribe() » retourne un object the type Subscription qui peut être utilisé à tout moment afin de se désabonner de l’Observable et ainsi arrêter l’envoie d’évènements. Pour ce désabonner de l’Observable il suffit d’utiliser la méthode « unsubscribe() » de la subscription.
Life-Cycle d’une subscription
A partir du moment qu’on s’abonne à un Observable avec la méthode « subscribe() » celui va continuer d’émettre des évènements jusqu’à ce que l’une des conditions suivantes soit rempli.
Fin de l’execution

L’Observable à fini son execution et n’a plus de valeurs à émettre. Dans ce cas l’Observable notifie les Subscribers en appellant la méthode « complete() ».
Erreur lors de l’execution

Si une erreur survient lors de l’execution de l’Observable, celui-ci envoie une erreur à ses Subscribers en utilisant la méthode « error() » à laquelle il passe l’erreur en question en paramètre, puis l’Observable termine son execution et n’envoie plus d’évènements ni de « complete ».
Tous les Subscribers se sont désabonnés

Une fois que tous les Subscribers abonnées à un Observable ont fait un unsubscribe, celui-ci arrête son execution et n’émet plus aucun évènement. Etant donné que certains Observables n’ont pas réellement de fin d’execution (par example: un observable de cliques, ou frappes au clavier) il est extrêmement important de se désabonner de tous les Observables une fois qu’on en a plus besoin, autrement on pourrait se retrouver avec des fuites de mémoire.
Exemple 1
Maintenant qu’on connait la théorie, regardons comment appliquer cela dans notre code. Pour cela on va créer un Observable qui va émettre les lettres de la phrase « Hello World », l’une après l’autre à interval régulier, puis l’Observable va émettre un signal completed.
Pour cela on va ouvrir le fichier app.component.ts d’une nouveau projet Angular et on va écrire le code suivant dans le constructeur de notre AppComponent:
import { Component } from '@angular/core';
import { Observable, Subscriber } from 'rxjs';
@Component({
selector: 'app-root',
standalone: true,
imports: [],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
constructor() {
const hello$ = new Observable<string>((subscriber: Subscriber<string>) => {
const stringToEmit = "Hello World";
for (let i=0; i<stringToEmit.length; i++) {
setTimeout(() => {
subscriber.next(stringToEmit[i]);
}, (i+1) * 1000);
}
setTimeout(() => {
subscriber.complete();
}, stringToEmit.length * 1000);
});
const helloObserver = {
next: (value: string) => {
console.log(value);
},
complete: () => {
console.log('Observable completed');
}
}
const subscriber = hello$.subscribe(helloObserver);
}
}
Il y a plusieurs choses à comprendre dans le code ci-dessus. Premièrement comme vous le voyez on instancie un nouvel Observable en faisant un new Observable et en lui passant en paramètre une méthode qui prend un Subscriber en paramètre. Cette méthode est celle que l’Observable exécutera lors d’un subscribe, et qui est responsable pour l’envoie des différents évènements et c’est cette méthode qui va faire appel aux méthodes « next », « error » et « complete » du Subscriber.
Il est important de noter qu’on stoque cet observable dans une variable dont le nom ce termine par un « $ », ceci est une convention de nommage qui est utilisé afin d’indiquer qu’une variable contient une instance d’un Observable.
En ce qui concerne le comportement de notre Observable, comme vous le voyez on traverse chaque lettre de notre phrase « Hello World » et on émet chacune de ses lettres grâce à la méthode next de notre Subscriber avec un seconde de décalage. Pour finir une seconde après la dernière lettre on fait appel à la fonction « complete() » afin de signaler que notre Observable a terminé son execution et n’émettra plus de valeurs.
Pour finir on déclare un Observer avec les méthodes next et complete, le méthode next imprime la valeur reçue dans la console et la méthode complete affiche un message dans la console indiquant que l’Observable a fini son execution.
Ensuite pour pouvoir commencer à recevoir des valeurs de l’Observable on y fait un « subscribe » en passant notre Observer en paramètre.

Si on regarde le résultat dans le navigateur on voit bien les différentes lettres qui sont émises à intervalles réguliers, ainsi que le message « Observable completed » qui s’affichent dans la console.
Maintenant essayons de simuler ce qui se passerai en cas d’erreur de la part de notre Observable. Pour cela on va ajouter un setTimeout à celui ci, qui va faire appel à la méthode « error » en luis passant un message d’erreur en paramètre, et on va également implémenter la méthode « error » dans notre Observer, qui va imprimer l’erreur à l’écran.
...
const hello$ = new Observable<string>((subscriber: Subscriber<string>) => {
const stringToEmit = "Hello World";
for (let i=0; i<stringToEmit.length; i++) {
setTimeout(() => {
subscriber.next(stringToEmit[i]);
}, (i+1) * 1000);
}
setTimeout(() => {
subscriber.complete();
}, stringToEmit.length * 1000);
setTimeout(() => {
subscriber.error("Error");
}, 5000);
});
const helloObserver = {
next: (value: string) => {
console.log(value);
},
error: (err: string) => {
console.log(err);
},
complete: () => {
console.log('Observable completed');
}
}
...
Maintenant, si on retourne voir notre navigateur on voit que les lettres « H », « e », « l », « l » et « o » sont affichées suivies du message « Error », puis plus rien. L’Observable arrête son execution et ne fait plus appelle, ni à la méthode « next », ni à la méthode « complete ».

Regardons maintenant l’effet qu’à un appel à « unsubscribe » sur notre Subscription. Pour cela on va ajouter juste après notre appel à « subscribe », un nouveau setTimeout qui s’exécutera au bout de 3 secondes et qui va effectuer un « unsubscribe ».
...
const subscriber = hello$.subscribe(helloObserver);
setTimeout(() => {
subscriber.unsubscribe();
}, 3000);
...
Cette fois si, si on retourne voir le résultat dans noter navigateur, on voit que les trois premières lettres sont affichées, et puis plus rien. L’appel à unsubscribe à bien interrompu l’execution de notre Observable et on n’obtient donc plus d’appels à notre Observer, que ce soit des appels à la méthode « next », « error » ou « complete ».

AsyncPipe
Pour clôturer cet exemple, regardons comment afficher les données reçues par un Observable dans nos template HTML. Pour cela on pourrait passer par une propriété de notre Component, qu’on assignerait avec les valeurs émises par notre Observable. Ce qui nous donnerait le code qui suit:
import { Component, OnDestroy } from '@angular/core';
import { Observable, Subscriber, Subscription } from 'rxjs';
@Component({
selector: 'app-root',
standalone: true,
imports: [],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent implements OnDestroy {
letter: string = '';
subscriber: Subscription | null = null;
constructor() {
const helloObservable$ = new Observable<string>((subscriber: Subscriber<string>) => {
const stringToEmit = "Hello World";
for (let i=0; i<stringToEmit.length; i++) {
setTimeout(() => {
subscriber.next(stringToEmit[i]);
}, (i+1) * 1000);
}
setTimeout(() => {
subscriber.complete();
}, stringToEmit.length * 1000);
});
const helloObserver = {
next: (value: string) => {
this.letter = value;
console.log(value);
},
complete: () => {
console.log('Observer completed');
}
}
this.subscriber = helloObservable$.subscribe(helloObserver);
}
ngOnDestroy(): void {
this.subscriber?.unsubscribe();
}
}
A noter ici l’unsubscribe que nous effectuer OnDestroy, donc si on quite cette page avant que l’Observable n’émette toutes ses valeurs on, s’y désabonnera et l’execution de celui-ci sera proprement arrêtée.
Maintenant que nos évènements sont stockés dans la propriété lettre on n’a plus qu’à l’utiliser dans notre fichier « app.component.html » :
{{ letter }}
Et là, comme attendu, les lettres de la phrase « Hello world » sont affichées les unes après les autres dans notre navigateur.

Cette méthode fonctionne, mais nous avons une manière plus simple d’utiliser l’observable directement dans notre navigateur en utilisant « PipeAsync ». Pour cela on va importer « PipeAsync » de « @angular/core », on va assigner notre Observable à une propriété de notre « AppComponent » et on va pouvoir effacer notre Observer ainsi que notre appel à la méthode « subscribe »:
import { Component } from '@angular/core';
import { Observable, Subscriber, Subscription } from 'rxjs';
import { AsyncPipe } from '@angular/common';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, SearchPageComponent, AsyncPipe],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
letter$: Observable<string> | null = null;
constructor() {
this.letter$ = new Observable<string>((subscriber: Subscriber<string>) =>{
const stringToEmit = "Hello World";
for (let i=0; i<stringToEmit.length; i++) {
setTimeout(() => {
subscriber.next(stringToEmit[i]);
}, (i+1) * 1000);
}
setTimeout(() => {
subscriber.complete();
}, stringToEmit.length * 1000);
});
}
}
Maintenant nous pouvons faire appel aux valeurs de notre Observable directement dans notre HTML on utilisant la syntaxe « {{ letter$ | async }} »:
{{ letter$ | async }}
La vous vous demandez sûrement comment cela peut fonctionner, étant donné qu’on n’a pas fait de subscribe à notre Observable. Et bien l’AsyncPipe se charge de faire le subscribe à l’observable, de récupérer les valeur émises par celui-ci, et lorsque le template n’est plus utilisé l’AsyncPipe se charge également d’effectuer l' »unsubscribe » à l’Observable.

La aussi si vous relancez votre navigateur vous verrez les lettre de la phrase « Hello World » s’afficher avec une seconde d’intervalle.
Exemple 2
Pour finir prenons un example plus parlant et voyons comment créer un Observable à partir d’un formulaire HTML. Pour ca on va tout d’abord importer le module « ReactiveFormsModule », et on va créer un « FormControl » pour un input qu’on créera dans un instant. Et à partir de ce « FormControl » on pourra utiliser la propriété « valueChanges » qui retourne un « Observable » qui émettra la valeur du champ input au fur et à mesure que l’utilisateur modifiera son contenu:
import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule, AsyncPipe],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
textFormControl = new FormControl('');
text$ = this.textFormControl.valueChanges;
}
Maintenant créons l’input dans notre fichier app.component.html et affichons le contenu émis par notre Observable:
<input [formControl]="textFormControl" />
Value: {{ text$ | async }}
Et si on se rend dans le navigateur et tape un texte dans l’input. On voit bien que le même texte s’afficher en dessous au fur et à mesure qu’on entre des caractères.

On aurait pu continuer et montrer comment créer un Observable à partir d’une requête HTTP en utilisant HttpClient, mais tout les Observables fonctionnent de la même façon, donc si vous avez compris comment les Observables fonctionnent, vous saurez le faire par vous même.
Dans le prochain chapitre on expliquera les opérateurs RxJS, et on verra comment enchainer plusieurs opérateurs afin de profiter pleinement de la puissance d’RxJS.