Show loader on every request in Angular 2

Providing feedback to our users is essential for good user experience. Because Angular is single page application when user navigates to new page browser won’t display usual loading animation.

Instead user will be presented with empty component and only after data is loaded (from backend) Angular will update views,.. unless you show loading animation from inside Angular.

Live Demo GIF

I know it sounds like simple functionality but I noticed that many developers are asking how to show loader on every http request, so lets start..


Custom Http Service

First thing needed is custom http service which extends built-in http class.

In one of my previous posts I wrote about custom http service for JWT authentication, same code is good for this post also. Check that tutorial here.

Full project is available on GitHub it includes code from all of my older posts, you can focus your attention on loader part only.

get(url: string, options?: RequestOptionsArgs): Observable<any> {
return super.get(this.getFullUrl(url), this.requestOptions(options))
.catch(this.onCatch)
.do((res: Response) => {
this.onSuccess(res);
}, (error: any) => {
this.onError(error);
})
.finally(() => {
this.onEnd();
});
}

Code above is showing how I currently handle get requests from custom Http service.

I have full control over this request so I can call show loader method before call to get method of the super class. I can also call hide loader method from onEnd method, cause at that moment request to the backend will be completed.

get(url: string, options?: RequestOptionsArgs): Observable<any> {
    this.showLoader();

//return super.get ....
}

In onEnd method I’ll call hideLoader method:

private onEnd(): void {
this.hideLoader();
}

I still did not create these loader methods in the custom class I’ll construct them as simple logging methods, just temporarily until I create loader service.

private showLoader(): void {
console.log('Show loader');
}
private hideLoader(): void {
console.log('Hide loader');
}
Console

Custom service works well

Loader

Idea is to have loader component which will be included in the main app component and using loader service Angular will trigger visibility state of loader component.

Loader component

This component holds html and css code for our loader, it will also posses subscription for loader state from Loader Service. Based on the state of loader it will show or hide html code of the loader.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { LoaderService } from './loader.service';
import { LoaderState } from './loader';
@Component({
selector: 'angular-loader',
templateUrl: 'loader.component.html',
styleUrls: ['loader.component.css']
})
export class LoaderComponent implements OnInit {
show = false;
private subscription: Subscription;
constructor(
private loaderService: LoaderService
) { }
ngOnInit() { 
this.subscription = this.loaderService.loaderState
.subscribe((state: LoaderState) => {
this.show = state.show;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}

Loader component html

I am a bit lazy so I will use Angular Material Progress bar component as the loader.

<div [class.loader-hidden]="!show">
<div class="loader-overlay">
<div>
<md-progress-bar mode="indeterminate" *ngIf="show"></md-progress-bar>
</div>
</div>
</div>

Angular Material requires imports in app.module.ts and core.module.ts, I’ll also have to include the theme in index.html file.

import { MaterialModule } from '@angular/material';

In the app.module.ts imports array item looks like this:

MaterialModule.forRoot()

And in core.module.ts imports array:

MaterialModule

I am using Core Module as place where I put services and components required for every action, in this case custom Http Service, Loader Service and Loader Component.

In index.html I am adding pink theme for Angular Material:

<link rel="stylesheet" href="https://unpkg.com/@angular/material/core/theming/prebuilt/pink-bluegrey.css" type="text/css">

Loader service

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { LoaderState } from './loader';
@Injectable()
export class LoaderService {
private loaderSubject = new Subject<LoaderState>();
loaderState = this.loaderSubject.asObservable();
constructor() { }
show() {
this.loaderSubject.next(<LoaderState>{show: true});
}
hide() {
this.loaderSubject.next(<LoaderState>{show: false});
}
}

This service is pretty simple, it posses private property for the subject and public property, observable for that subject.

LoaderState is only an interface which holds one boolean property:

export interface LoaderState {
show: boolean;
}

Loader css

I want to display loading progress bar right under address bar in full width, so it will be positioned in absolute manner.

.loader-hidden {
visibility: hidden;
}
.loader-overlay {
position: absolute;
width:100%;
top:0;
left: 0;
opacity: 1;
z-index: 500000;
}
Directory structure

All loader related files are stored in dedicated directory inside the core.

Importing loader

Because loader is part of the Core Module I will import it there:

@NgModule({
imports: [
CommonModule,
MaterialModule
],
exports: [
LoaderComponent
],
declarations: [
LoaderComponent
],
providers: [
LoaderService,
{
provide: HttpService,
useFactory: httpServiceFactory,
deps: [XHRBackend, RequestOptions, LoaderService ]
}
]
})
export class CoreModule { }

And CoreModule is imported in imports array of app.module.ts.

Back to HttpService

I need to inject LoaderService through the constructor:

constructor(
backend: XHRBackend,
defaultOptions: AngularReduxRequestOptions,
private loaderService: LoaderService
) {
super(backend, defaultOptions);
}

Show/Hide methods

I already called visibility methods from http request, now I’ll update them:

private showLoader(): void {
this.loaderService.show();
}
private hideLoader(): void {
this.loaderService.hide();
}

Http service factory

Services which extend build-in classes are required to have service provider defined and the factory. I am storing my factories into _factories directory. From CoreModule decorator you could see how am I defining service provider.

And this is the factory:

import { XHRBackend } from '@angular/http';
import { AngularReduxRequestOptions } from '../core/angular-redux-request.options';
import { HttpService } from '../core/http.service';
import { LoaderService } from '../core/loader/loader.service';
function httpServiceFactory(backend: XHRBackend, options: AngularReduxRequestOptions, loaderService: LoaderService ) {
return new HttpService(backend, options, loaderService);
}
export { httpServiceFactory };

With this Http service is completed but we will see no loader cause it is not used in the App component.

Inside app.component.html I’ll insert angular-loader tag on the first line.

Closing

Now we have functional loader on every get request, if you are curious to see how it’ll look checkout Live Demo. Try clicking Load New button few times to see it in action.

Source code

Entire source code is available on GitHub, feel free to clone, fork and contribute. And please do not open new issues because of that pink loader color, my designer is on vacation ;)