9

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;
}
2
  • I think you have to post using { reportProgress: true,} and subscribe to events. check angular.io/guide/http#listening-to-progress-events Commented Jan 19, 2018 at 8:44
  • @Eliseo, I added my ApiService code to the question. I'm not sure where I'd set reportProgress because RequestOptions doesn't have a reportProgress property. Surely I don't have to change all the code in that service to use HttpRequest instead of Http. Commented Jan 19, 2018 at 11:22

4 Answers 4

5

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.

Sign up to request clarification or add additional context in comments.

Comments

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

7 Comments

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.
sorry, I feel stupid for not having noticed before: interceptor only work using HttpClient (NOT http). Change your ApiService constructor
Switching the HttpClient works...thanks. However, the spinner never gets hidden. Is do the appropriate place to hide the spinner?
I don't be sure, perhaps using finally... I just "ajust" the example of angular.io/guide/http#logging
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?
|
0

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 Comments

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?
@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!
Do you want me to create a seperate SO post?
I've created a new post with my code: stackoverflow.com/questions/52876207/…
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.