Long, long ago, I blogged about Angular 1.x Request Interceptors and how they can be used to display a loading indicator. I really liked that mechanism. You could intercept any request whether you made it or it was made by the framework. Fast-forward to today, and things are significantly different with Angular2.
Angular2 simply does not have request interceptors. Most of Angular2’s (Angular from here out) Http calls are handled by an injected service provider called, aptly, Http. Fortunately, with Angular’s dependency injection framework, we can replace the Http provider with our own provider.
In replacing the Http provider, my goal is to create something very similar to my old Request Interceptor. When a request is detected, I want to use “spin.js” to display a modal spinner to the user. With this in mind, I’m reusing the same methods I used for turning the modal on or off. The big difference is tracking whether or not there are pending requests. At any rate, let’s take a look at the implementation.
The custom provider extends Http. Http has many methods we can implement and we define a constructor to indicate which “backend” we’re utilizing. Angular has a few different backends for XHR, JSONP, and such. The XHRBackend is appropriate in most cases since we will be making XHR requests. Suffice it to say, that these are framework details that are necessary to construct our “HttpService.”
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/finally'; import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '@angular/http'; declare var $: any; @Injectable() export class HttpService extends Http { public pendingRequests: int = 0; public showLoading: bool = false; constructor(backend: XHRBackend, defaultOptions: RequestOptions) { super(backend, defaultOptions); } request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { return this.intercept(super.request(url, options)); }
The Http provider has methods for get/post/put and other HTTP verbs, but, for the sake of brevity, I have listed only the general request method that is being implemented. You can see in this method that we expect the method to return an Observable. We’ll use the base (super methods) class’s methods in our HttpService to create the Observable<Response>, but then attach catch/do/finally handlers in order to “intercept” the request. The intercept method, then, looks like the code below.
intercept(observable: Observable<Response>): Observable<Response> { console.log("In the intercept routine.."); this.pendingRequests++; return observable .catch((err, source) => { console.log("Caught error: " + err); }) .do((res: Response) => { console.log("Response: " + res); }, (err: any) => { console.log("Caught error: " + err); }) .finally(() => { console.log("Finally.. delaying, though.") var timer = Observable.timer(1000); timer.subscribe(t => { this.turnOffModal(); }); }); }
During the request’s processing, we are using catch/do/finally blocks do increment or decrement our “pendingRequests” counter. This is used so that if we have a lot of simultaneously requests, we can effectively wait until they are all complete before hiding our spinning modal. You’ll also notice that I add an arbitrary delay timer. Without this, the requests will finish so quickly in the demo that you won’t see them.
The modal spinner toggling code is straight forward and, basically, the same as the old request interceptor code. It makes a few checks against the pending requests, determines, in the cases of turning off the modal, if it should decrement, and then uses the spinner jquery plugin to display/hide the spinner.
private turnOnModal() { if (!this.showLoading) { this.showLoading = true; $('body').spin("modal", "#FFFFFF", "rgba(51, 51, 51, 0.1)"); console.log("Turned on modal"); } this.showLoading = true; } private turnOffModal() { this.pendingRequests--; if (this.pendingRequests <= 0) { if (this.showLoading) { $('body').spin("modal", "#FFFFFF", "rgba(51, 51, 51, 0.1)"); } this.showLoading = false; } console.log("Turned off modal"); }
Beyond this basic implementation, we then must tell Angular to use our HttpService rather than the built-in Http provider. This is done in the app.module.
import { Http, HttpModule, RequestOptions, XHRBackend } from '@angular/http'; import { HttpService } from './services/http.service'; @NgModule({ imports: [BrowserModule, FormsModule, ReactiveFormsModule, JsonpModule, HttpModule, NgbModule.forRoot(), routing], declarations: [ AppComponent, Route1Component, Route2Component, Route3Component, DialogComponent, Multiselect, FilterPipe, EqualPipe ], providers: [ { provide: APP_BASE_HREF, useValue : document.location.pathname }, { provide: Http, useFactory: (backend: XHRBackend, options: RequestOptions) => { return new HttpService(backend, options); }, deps: [XHRBackend, RequestOptions] }, NavigationService, DialogService, ApiService], entryComponents: [DialogComponent], bootstrap: [ AppComponent ], })
The salient points here are only a few. First, HttpModule must be in the imports list. Second, the providers list must include that we are providing Http, specifying its factory method which returns our HttpService, and the constructor dependencies. A fully working plunk is below.
If you check the console, you can see the logic flow.
Hey stephan
Thanks for this nice tutorial. It also help me to understand the concept of interceptors.
Glad it helped you. Working through a “port” of the interceptors I was using in 1.x helped me too. The Angular 2/4 approach to most things reminds me a lot of .NET Core injection and service pattern.