Angular HTTP Interceptor - Display spinner in multi-module app

14,883

Solution 1

The issue was the ApiService was using the Http from @angular/http instead of HttpClient from @angular/common/http.

So the ApiInterceptor has nothing to intercept.

Solution 2

forget reportProgress:true. The problem is that we have to discriminate the event of "do". Moreover, we must take a count of the calls, so the interceptor must be like

contador: number = 0;

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this.contador++;
        if (this.contador === 1) {
            this.spinner.show();
        }
        let handleObs: Observable<HttpEvent<any>> = next.handle(req);
        handleObs
        .catch((err: any) => { //If an error happens, this.contador-- too
            this.contador--;
            return Observable.throw(err);
        })
        .do(event => {
           if (event instanceof HttpResponse) { //<--only when event is a HttpRespose
              this.contador--;
              if (this.contador==0)
                 this.spinner.hide();
           }
        });

        return handleObs;
    }
Share:
14,883
TheMagnificent11
Author by

TheMagnificent11

I've worked in .Net roles since 2007 (mainly ASP.Net but also some WinForms back in the day). I do mainly ASP.Net MVC and Web API development in my current role. However, what I enjoy most is working with Angular (2+) and ASP.Net Core for my API, which is the tech stack I'm using in my first Github repo: https://github.com/TheMagnificent11/LunchVoting

Updated on July 26, 2022

Comments

  • TheMagnificent11
    TheMagnificent11 almost 2 years

    I'm trying to display the ng4-loading-spinner spinner for HTTP calls made to my API.

    I based my code on the examples in the following links:

    My Angular 5 app has multiple multiple modules. The HTTP interceptor is in the "services" module.

    I think I'm having a dependency injection problem because the code HTTP interceptor code doesn't get executed when I debug my code with Chrome Dev Tools.

    api-interceptor.ts

    import 'rxjs/add/operator/do';
    import 'rxjs/add/operator/catch'
    import { Observable } from 'rxjs/Observable';
    import { Injectable } from '@angular/core';
    import {
        HttpEvent,
        HttpInterceptor,
        HttpHandler,
        HttpRequest,
        HttpResponse
    } from '@angular/common/http';
    import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
    
    @Injectable()
    export class ApiInterceptor implements HttpInterceptor {
    
        private count: number = 0;
    
        constructor(private spinner: Ng4LoadingSpinnerService) { }
    
        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            this.count++;
    
            if (this.count == 1) this.spinner.show();
    
            let handleObs: Observable<HttpEvent<any>> = next.handle(req);
    
            handleObs
                .catch((err: any) => {
                    this.count--;
                    return Observable.throw(err);
                })
                .do(event => {
                    if (event instanceof HttpResponse) {
                        this.count--;
                        if (this.count == 0) this.spinner.hide();
                    }
                });
    
            return handleObs;
        }
    
    }
    

    api.service.ts

    import { Injectable, Inject } from '@angular/core';
    import { Http, Response, Headers, RequestOptions } from '@angular/http';
    import { Observable } from 'rxjs/Observable';
    
    import { TokenService } from './token.service';
    
    @Injectable()
    export class ApiService {
    
        constructor(
            private http: Http,
            private session: TokenService,
            @Inject('BASE_URL') private baseUrl) { }
    
        get(entityRoute: string): Observable<Response> {
            let apiRoute = this.getApiRoute(entityRoute);
            let options = this.generateRequestOptions();
    
            return this.http.get(apiRoute, options);
        }
    
        post<T>(entityRoute: string, entity: T): Observable<Response> {
            let apiRoute = this.getApiRoute(entityRoute);
            let options = this.generateRequestOptions();
    
            return this.http.post(apiRoute, entity, options);
        }
    
        put<T>(entityRoute: string, entity: T): Observable<Response> {
            let apiRoute = this.getApiRoute(entityRoute);
            let options = this.generateRequestOptions();
    
            return this.http.post(apiRoute, entity, options);
        }
    
        private getApiRoute(entityRoute: string): string {
            return `${this.baseUrl}api/${entityRoute}`;
        }
    
        private generateRequestOptions(): RequestOptions {
            let headersObj = null;
            let accessToken = this.session.getAccessToken();
    
            if (accessToken) {
                headersObj = {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + accessToken
                };
            } else {
                headersObj = {
                    'Content-Type': 'application/json'
                };
            }
    
            let headers = new Headers(headersObj);
            return new RequestOptions({ headers: headers });
        }
    
    }
    

    services.module.ts

    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { HttpModule } from '@angular/http';
    import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
    
    import {
        ApiInterceptor,
        ApiService,
        TokenService
    } from './index';
    
    @NgModule({
        imports: [
            CommonModule,
            HttpModule,
            Ng4LoadingSpinnerModule
        ],
        providers: [
            ApiInterceptor,
            ApiService,
            TokenService
        ]
    })
    export class ServicesModule { }
    
    export * from './index';
    

    app.module.ts

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { HTTP_INTERCEPTORS } from '@angular/common/http';
    import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner';
    
    import { BootstrapModule } from './bootstrap/bootstrap.module';
    import { ServicesModule, ApiInterceptor } from './services/services.module';
    import { AppComponent } from './app-component';
    
    @NgModule({
        bootstrap: [ AppComponent ],
        imports: [
            BrowserModule,
            Ng4LoadingSpinnerModule.forRoot(),
            BootstrapModule,
            ServicesModule
        ],
        providers: [
            {
                provide: 'BASE_URL',
                useFactory: getBaseUrl
            },
            {
                provide: HTTP_INTERCEPTORS,
                useClass: ApiInterceptor,
                multi: true,
            }
        ]
    })
    export class AppModule {
    }
    
    export function getBaseUrl(): string {
        return document.getElementsByTagName('base')[0].href;
    }
    
  • TheMagnificent11
    TheMagnificent11 over 6 years
    I added this code but it doesn't solve my dependency injection problem. The ApiInterceptor never gets hits (when I debug) and the spinner never shows. I have updated my question to include your code.
  • Eliseo
    Eliseo over 6 years
    sorry, I feel stupid for not having noticed before: interceptor only work using HttpClient (NOT http). Change your ApiService constructor
  • TheMagnificent11
    TheMagnificent11 over 6 years
    Switching the HttpClient works...thanks. However, the spinner never gets hidden. Is do the appropriate place to hide the spinner?
  • Eliseo
    Eliseo over 6 years
    I don't be sure, perhaps using finally... I just "ajust" the example of angular.io/guide/http#logging
  • TheMagnificent11
    TheMagnificent11 over 6 years
    Thanks, that worked. Do you want to post another answer saying that the issue is that I need to use HttpClient instead of Http in my ApiService and I'll mark it as the solution?
  • nick gowdy
    nick gowdy over 5 years
    When I add subscribe to the chain, I see in the network tab of my browser that it's doing all requests twice. Does anybody know why this would be?
  • Geiv Tomço
    Geiv Tomço over 5 years
    @nickgowdy Can you please provide us a screenshot of the network tab? Note that the whole code above, including the .subscribe() method, only "listens" to the main Http calls, it doesn't mutate them as far as this code is concerned. We can only assume that you are referring to the fact that XHR requests generate two requests, one being OPTION (not the actual request, just headers prior to the real request) and the other being the actual request (either GET/POST etc. with the actual data). Anyway I assume we need a bit more information please. Best regards!
  • nick gowdy
    nick gowdy over 5 years
    Do you want me to create a seperate SO post?
  • nick gowdy
    nick gowdy over 5 years
    I've created a new post with my code: stackoverflow.com/questions/52876207/…
  • Admin
    Admin almost 4 years
    It is blinking if so lot fast requests/responses
  • Eliseo
    Eliseo almost 4 years
    @MeganClerc, there're another aproach, for me the best, that it's create a new operator -no interceptor-, see stackoverflow.com/questions/60207721/…