Angular HTTP 拦截器 - 在多模块应用程序中显示微调器

Angular HTTP Interceptor - Display spinner in multi-module app

我正在尝试显示对我的 API 进行的 HTTP 调用的 ng4-loading-spinner 微调器。

我的代码基于以下链接中的示例:

我的 Angular 5 应用程序有多个模块。 HTTP 拦截器位于 "services" 模块中。

我认为我遇到了依赖项注入问题,因为当我使用 Chrome 开发工具调试我的代码时,代码 HTTP 拦截器代码没有被执行。

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

忘记 reportProgress:true。问题是我们必须区分 "do" 的事件。此外,我们必须对调用进行计数,因此拦截器必须像

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

问题是 ApiService 使用 @angular/httpHttp 而不是 @angular/common/httpHttpClient

所以 ApiInterceptor 没有什么可拦截的。

对于跟进此问题的每个人来说,OP 的代码现在工作正常,除了加载程序似乎没有隐藏的其余问题。解决方法是 subscribe 到 Observable,在 .catch .do 链之后,像这样:

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;

在此之后,代码应该可以正常工作,当计数器达到 0 时加载程序将隐藏。也感谢以上所有答案的贡献!

对于遇到计数器永远不会再次归零的问题的任何人,即使没有待处理的请求: 当 增加计数器 :

时,我不得不额外检查事件类型
 if (event instanceof HttpResponse) {
    this.counter.dec();
 } else {
    this.counter.inc();
 }

否则,我遇到了 HttpResponse 也增加了我的计数器的情况。 通过以上检查,我所有组件的计数器都归零了。

此外,请确保返回的 http 错误(例如 401)也会减少您的计数器,否则计数器将永远不会再次达到零。 为此:

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