--

Developing a front-end using Angular usually means that there is a back-end which serves the data. This means that multiple HTTP calls to the back-end will be done while navigating on the web site.

It is usually a good practice to inform the user that data are currently loading. Displaying a loader tells the user why the component is currently empy => data are currently loading.

You may say that this is another article talking about this subject. And you’re right there are already goods article talking about loader in angular (Show loader on every request in Angular 2 or Angular 2 HTTP request wrapper with Loader). And I personnaly used these approaches on my previous projects. However, during my last project I faced a concurrency issue not treateb with these approches. This is the goal of this article.

For this article we are going to use the Angular material progress spinner that will be displayed in an overlay.

Lest’s start by reviewing the basic approach.

1. Spinner Overlay component

The spinnerOverlayComponent is a simple component using mat-progress-spinner.

2. The spinner service

We’ve made a spinnerOverlayComponent that content our mat-progress-spinner. It’s time to create a service that will show/hide this spinner in an overlay.

This service provide tow methodes ‘show’ and ‘hide’ that allow us to display/hide our spinnerOverlayComponent in an overlay centered vertically and horizontally on the screen.

Note: The overlay is created insinde a Promise as a work around to avoid a warning regarding ExpressionChangedAfterItHasBeenCheckedError. It is not the subject here, so I will not go into details on this subject.

3. The spinner interceptor

We alredy have a component and a service to display/hide our spinner in an overlay. The only peace missing is triggering our service on every HTTP request.

For that we will use HttpInterceptor provided by Angular:

Thanks to this interceptor, the spinner overlay will be displayed at the begining of every request and hide when the request is completed.

But what may happen if several requests are sent in parallel ?

If multiple requests are sent in parallel, we’ll face some issues:

  1. For each request sent a new Overlay will be created one on top of the other :-(
  2. When the 1st request will end the hide method will be call => overlayRef of the SpinnerOverlayService will be set to undefined causing an exception when the 2nd request will finalize.

So what to do ?

Updating our service to check overlayRef attribut before actions like the following ?

=> NO

Clearly no, has this won’t avoid concurrency issue. If the first and 2nd request are completed quite at the same time we can have the following senario:

  • request 1 is completed and call the hide method (let’s say hide1)
  • request 2 is completed and call the hide method (let’s say hide2)
  • in hide1 the overlayRef.detach is called
  • in hide2 overlayRef is not yet set to ‘undefined’ so it enter in the if statement
  • in hide1 overlayRef is set to ‘undefined’
  • in hide2 overlayRef.detach is called but overlayRef is now ‘undefined’ which leads to an exception :-(

Implementing a counter, is neither a good idea for the same reason.

The solution => use RxJs

A solution I found to avoid concurrency issue and, in the same time, avoiding having a complexe code for managing concurency is to use RxJs with ‘share’ operator.

The solution is to implement an observable using ‘NEVER’ observable constant in the SpinnerOverlayService:

Once the fisrt subscriber will subscribe the observable, thanks to ‘defer’, the spinner overlay will be displayed. And the spinner overlay will stay displayed as long as the observable is subscribed thanks to ‘NEVER’ which emits no items to the Observer and never completes.

In row number 8, the statement ‘.pipe(share())’ returns a new Observable that multicasts (shares) the original Observable. It means that if another subriber subscribe to the observable (while it is already subscibed) the statement ‘this.show()’ will not be executed a second time. It also means that in order to go through the ‘finalize’ step, all subscribers need to unsubscribe the observable.

Then, we need to update our HttpInterceptor to use our new Observable:

With this new version of the interceptor, while processing every request, a new subscription to our spinner$ observable is made.

Then when all requests are completed, the overlay will be hide as there are no more subsciber, the finalize step of the spinner$ observable will be executed.

Finale note

  1. This is my 1st publication, therefore, please be indulgent.
  2. If you you find any improvement that can be done, feel free to comment as well

Thanks for reading.

Edit

As mentionned by Derek Arthur (thanks to him), I forgot to mention that as we are using an interceptor in this solution, we need to declare it as a provider in our app.module, ie:

Here after are the full implementation using Angular 11.2 and RxJs 6.6.0:

And the Demo link :

--

--