fabernovel loader

Oct 13, 2017 | 3 min read

Tech

Managing Refresh in an Angular Application

Emilie Paillous

Developer


FABERNOVEL TECHNOLOGIES
In an application, the question of "data freshness" is of paramount importance. Regularly, it is necessary to implement an "automatic refresh" behavior, which would allow the new data to be recovered in a specific time period in a transparent way for the user.

Within this time period, anyone who needs to display these data would retrieve a data cache, and subscribe to any data updates. This also avoids redoing an expensive network call to retrieve the same data between two screens. Finally, if the network call fails, the application could use the latest recovered data and display an error strip stating the date of the last update, rather than blocking the user in his path.

Here is how we implemented this behavior using Angular and reactive programming.

The service

 

Imagine that the data we want to retrieve is articles. The ArticleService service that is responsible for retrieving this data is provided within the core module as it is used by several components. It mainly outlines an « articlesSource » attribute that is an observable of an ArticlesState object.

 

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

This object will allow us to keep into cache the items recovered in the last thirty seconds, and an error state to know whether the last recovery went well.

 

The stream

In the ArticleService builder, the « articlesSource » observable is defined in the following way:

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

The requestArticles method is defined in the following way:

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

 

Let us analyze this chain of observables:

  • timer (0, 30000) initializes an observable that sends an event every 30 seconds
  • The flatmap operator chained with the timer can call the requestArticles method every 30 seconds
  • The map operator then transforms the response of the webservice into an ArticlesState object
  • The share operator allows several observers to subscribe to this stream. With each new observer, there is no new creation of streams (therefore, no new network call) the observer simply registers on the existing stream.

 

In the event of a webservice error, the stream returned by this.http.get (‘/ articles’) stops and falls into the catch. The latter returns a new observable from the error received. This avoids stopping the main stream if an error occurs.

Here is how the extractArticles () method is implemented:

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;
 }

Therefore, in the event of success, the received articles objects are stored in the articlesState object. Otherwise, this object is shown as error. Either way, the extractArticles method returns the articlesState object.

Observers

 

A component wishing to subscribe to the articles has to proceed as follows:

 

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

The component must keep a reference of the subscription, as he has to destroy this subscription on the OnDestroy to avoid memory leaks.

At this point, there is a remaining issue. Indeed, when there aren’t any observers anymore registered on the observable stream, it no longer has any reason to exist. Thus, the next time an observer subscribes to articlesSource, the stream will resume at 0. Let’s take an example of a path:

B subscribes after A
  • a component A subscribes to the articlesSource stream. Every 30 seconds, A receives the new data from the stream
  • Before being destroyed, A unsubscribes from the stream
  • a component B subscribes to the articlesSource stream

The stream is destroyed before B is registered, so when B registers, a network call is made to retrieve « fresh » data when in fact the data was retrieved just a few seconds before A’s destruction. If B subscribes inside a 30 seconds interval, it correctly gets cache data before the end of the interval, without reloading a network call :

B subscribes at the same time as A and get data from cache (red arrow)

To avoid this, a SelfDestroySubscriber object is used, which is registered at the same time as the first observer, and is unsubscribed at the same time as the last observer.

 

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();
     }
   }
}

 

The ArticleService class now includes two methods:

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();
}

Therefore, each component calls subscribeToArticles to be notified every thirty seconds of new data, and unsubscribeArticles before being destroyed.

Any questions about the managing refresh with Angular ?

Contact Emilie
This article belongs to a story
logo business unit

FABERNOVEL TECHNOLOGIES

150 talents to face technological challenges of digital transformation

next read