Angular HTTP Interceptor - Display spinner in multi-module app

烈酒焚心 提交于 2019-11-30 13:35:50

问题


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:

  • https://angular.io/guide/http#intercepting-all-requests-or-responses
  • Angular4: Using HttpClient's interceptor to setup a spinner

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;
}

回答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.




回答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;
    }



回答3:


For everyone following up to this issue, the OP's code now is working fine, except for the remaining issue that the loader doesn't seem to hide. The fix to that is to subscribe to the Observable, after the .catch .do chain, like so:

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();
        }
    })
    .subscribe(); /* <---------- ADD THIS */

return handleObs;

After this, the code should be working fine, and the loader will hide when the counter reaches 0. Thanks to all the above answers for their contribution as well!




回答4:


For anyone who has the problem with the counter never reaching zero again, even though no requests are pending: I had to additionaly check for the type of event when increasing the counter:

 if (event instanceof HttpResponse) {
    this.counter.dec();
 } else {
    this.counter.inc();
 }

Otherwise, i had the case of a HttpResponse also increasing my counter. With the above check my counter is going back to zero on all my components.

Also, make sure a returning http error (e.g. 401) is also decreasing your counter, otherwise the counter will never reach zero again. To do so:

return next.handle(req).pipe(tap(
  (event: HttpEvent<any>) => {
    if (event instanceof HttpResponse) {
        this.counter.dec();
    }
  },
  err => {
    if (err instanceof HttpErrorResponse) {
      this.counter.dec();
    }
  }
));


来源:https://stackoverflow.com/questions/48336800/angular-http-interceptor-display-spinner-in-multi-module-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!