Article
Création de logiciels
13.10.2017
Perturbation continuum temporel
5 minutes
Temps d'un café
Gestion du rafraîchissement dans une application Angular
Emilie Paillous
Note : Ce contenu a été créé avant que Fabernovel ne fasse partie du groupe EY, le 5 juillet 2022.

Au sein de cet intervalle, quiconque aurait besoin d’afficher ces données récupérerait un cache, et s’abonnerait aux éventuelles mises à jour des données. Ceci permet également d’éviter de refaire un appel réseau coûteux pour récupérer les même données entre deux écrans. Enfin, en cas d’échec de l’appel réseau, plutôt que de bloquer l’utilisateur dans son parcours, l’application pourrait utiliser les dernières données récupérées et afficher un bandeau d’erreur indiquant la dernière date de mise à jour.

Voici comment nous avons implémenté ce comportement en utilisant Angular et la programmation réactive.

Le service

Imaginons que les données que nous souhaitons récupérer sont des articles. Le service ArticleService qui est responsable de la récupération de ces données est fourni au sein du core module, car il est utilisé par plusieurs composants. Il expose principalement un attribut “articlesSource” qui est un observable d’un objet ArticlesState.

export class ArticlesState { articles: Article[]; isInError: boolean; constructor(articles: Article[], error: boolean) { this.articles = articles; this.error = error; } }

Cet objet va nous permettre de garder en cache les articles récupérés dans les dernières trente secondes, et un état d’erreur permettant de savoir si la dernière récupération s’est bien passée.

Le flux

Dans le constructeur d’ArticleService, on définit l’observable “articlesSource” de la manière suivante :

this.articlesSource = Observable.timer(0, 30000) .flatMap(event => this.requestArticles()) .map(response => this.extractArticles(response)) .share();

La méthode requestArticles est définie comme suit :

private requestArticles(): Observable<any> { return this.http.get(‘/articles’) .catch(error => Observable.of(error)); }

Analysons ce chaînage d’observables :

  • Observable.timer(0, 30000) initialise un observable émettant un événement toutes les 30 secondes
  • L’opérateur flatmap chaîné avec le timer permet d’appeler la méthode requestArticles toutes les 30 secondes
  • L’opérateur map à la suite transforme la réponse du webservice en objet ArticlesState
  • L’opérateur share permet à plusieurs observateurs de s’abonner à ce flux. A chaque nouvel observateur, il n’y a pas de nouvelle création de flux (donc, pas de nouvel appel réseau) , l’observateur s’inscrit simplement sur le flux déjà existant.

En cas d’erreur du webservice, le flux renvoyé par this.http.get(‘/articles’) s’arrête et tombe dans le catch. Celui-ci renvoie un nouvel observable à partir de l’erreur reçue. Ceci permet d’éviter l’arrêt du flux principal en cas d’erreur.

Voici l’implémentation de la méthode extractArticles() :

private extractArticles(res: Response) { if (res == null || (res.status < 200 || res.status >= 400)) this.articlesState.isInError = true; return this.articlesState; } this.articlesState.articles = res.json().articles; this.articlesState.isInError = false; return this.articlesState; }

Ainsi, en cas de succès, on stocke les objets articles reçu dans l’objet articlesState, sinon on marque cet objet en erreur. Dans tous les cas, la méthode extractArticles renvoie l’objet articlesState.

Observateurs

Un composant souhaitant s’abonner aux articles procède comme suit :

this.subscription = this.articleService.articlesSource .subscribe(articlesState => { if (articlesState.isInError) { this.errorMessage = 'Impossible de rafraîchir les données'; } else { this.articles = articlesState.articles; } });

Le composant doit garder une référence sur l’abonnement, car il doit détruire cette souscription dans le OnDestroy pour éviter les fuites mémoires.

A ce stade, il reste encore un problème. En effet, lorsqu’il n’y a plus d’observateurs inscrit sur le flux observable, celui-ci n’a plus de raison d’exister. Ainsi, la prochaine fois qu’un observateur souscrira à articlesSource, le flux reprendra à 0. Prenons un exemple de parcours :

  • Un composant A s’inscrit au flux articlesSource. Toutes les 30 secondes, A reçoit les nouvelles données du flux
  • Avant d’être détruit, A se désabonne du flux
  • Un composant B s’inscrit au flux articlesSource

Le flux est détruit avant l’inscription de B, donc lorsque B s’inscrit, un appel réseau est fait pour récupérer des données “fraîches” alors qu’en réalité les données ont été récupérées il y’a a peine quelques secondes avant que A ne soit détruit (voir schéma précédent). Si B s'inscrit dans un intervalle où A continue de recevoir des données, il reçoit bien les données en cache avant la fin du prochain intervalle de 30 secondes, sans forcer un appel réseau, comme sur le schéma suivant :

Pour éviter cela, on utilise un objet SelfDestroySubscriber qui s’inscrit en même temps que le premier observateur, et se désinscrit en même temps que le dernier observateur.

export class SelfDestroySubscriber { private numberOfOtherSubscribers = 0; private subscription: Subscription; constructor() { } subscribeToObservable(observable: Observable<any>) { this.subscription = observable.subscribe(any => this.checkNumberOfOtherSubscribers()); } addSubscriber(observable: Observable<any>) { if (this.numberOfOtherSubscribers === 0) { this.subscribeToObservable(observable); } this.numberOfOtherSubscribers++; } removeSubscriber() { this.numberOfOtherSubscribers--; } private checkNumberOfOtherSubscribers() { if (this.numberOfOtherSubscribers === 0) { this.subscription.unsubscribe(); } } }

La classe ArticleService expose désormais deux méthodes :

subscribeToArticles(callback: (articlesState: ArticlesState) => void): Subscription { this.selfDestroySubscriber.addSubscriber(this.articlesSource); return this.articlesSource.subscribe((state) => callback(state)))); } unsubscribeArticles(subscription: Subscription) { this.selfDestroySubscriber.removeSubscriber(); subscription.unsubscribe(); }

Chaque composant appelle donc subscribeToArticles pour être notifié toutes les 30 secondes de nouvelles données, puis unsubscribeArticles avant d’être détruit.

No items found.
Pour aller plus loin :