Angular 4+ has a new HttpClient which supports HttpInterceptors. This allows you to insert code that will be run whenever you make a HTTP request.
It is important to notice that HttpRequest are not long-lived Observables, but they terminate after the response. Furthermore, if the observable is unsubscribed before the response has returned, the request is cancelled and neither of the handlers are being processed. You may therefore end up with a "hanging" loader bar, which never goes away. This typically happens if you navigate a bit fast in your application.
To get around this last issue, we need to create a new Observable to be able to attach teardown-logic.
We return this rather than the original Observable. We also need to keep track of all requests made, because we may run more than one request at a time.
We also need a service which can hold and share the state of whether we have pending requests.
@Injectable()
export class MyLoaderService {
// A BehaviourSubject is an Observable with a default value
public isLoading = new BehaviorSubject<boolean>(false);
constructor() {}
}
The Interceptor uses the MyLoaderService
@Injectable()
export class MyLoaderInterceptor implements HttpInterceptor {
private requests: HttpRequest<any>[] = [];
constructor(private loaderService: MyLoaderService) { }
removeRequest(req: HttpRequest<any>) {
const i = this.requests.indexOf(req);
this.requests.splice(i, 1);
this.loaderService.isLoading.next(this.requests.length > 0);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.requests.push(req);
this.loaderService.isLoading.next(true);
return Observable.create(observer => {
const subscription = next.handle(req)
.subscribe(
event => {
if (event instanceof HttpResponse) {
this.removeRequest(req);
observer.next(event);
}
},
err => { this.removeRequest(req); observer.error(err); },
() => { this.removeRequest(req); observer.complete(); });
// teardown logic in case of cancelled requests
return () => {
this.removeRequest(req);
subscription.unsubscribe();
};
});
}
}
Finally, in our Component, we can use the same MyLoaderService and with the async operator we do not even need to subscribe. Since the source value we want to use is from a service, it should be shared as an Observable so that it gets a rendering scope/zone where it is used. If it is just a value, it may not update your GUI as wanted.
@Component({...})
export class MyComponent {
constructor(public myLoaderService: MyLoaderService) {}
}
And an example template using async
<div class="myLoadBar" *ngIf="myLoaderService.isLoading | async">Loading!</div>
I assume you know how to provide services and set up modules properly.
You can also see a working example at Stackblitz