Turismo98
Turismo98

Reputation: 158

Showing a spinner during HTTP requests in Angular7

I'm trying to show a spinner during the application's HTTP requests on Angular 7. I've created a HttpInterceptor class, a loader service and a loader component and a Subject representing the state of the spinner (shown or hidden). However there seems to be a problem between the loader service and the loader component

The HttpInterceptor seems to be working fine as the console outputs true and false states (code below), however there seems to be a problem with the loader service and loader component. I've used an ngIf directive to show or hide the spinner according to the state of a Subject.

LoaderService:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class LoaderService {

  isLoading = new Subject<boolean>();

  show() {
    console.log('true');
    this.isLoading.next(true);
  }

  hide() {
    console.log('false');
    this.isLoading.next(false);
  }

  constructor() { }
}

LoaderComponent

import { Component, OnInit } from '@angular/core';
import {LoaderService} from '../loader.service';
import { Subject } from 'rxjs';

@Component({
  selector: 'app-loader',
  templateUrl: './loader.component.html',
  styleUrls: ['./loader.component.css']
})
export class LoaderComponent implements OnInit {

  isLoading: Subject<boolean> = this.loaderService.isLoading;

  constructor(private loaderService: LoaderService) { }

  ngOnInit() {
  }

}
<div *ngIf="isLoading | async " class="d-flex justify-content-center">
  <div class="spinner-border" role="status">
    <span class="sr-only">Loading...</span>
  </div>
</div>

Currently the spinner isn't showing or hiding at all. I'm only getting 'true' in the console when the loading begins, then 'false' when it ends. I've included the loader selector in the app.component.html file. It should be working properly. Did I do something wrong?

Upvotes: 3

Views: 2622

Answers (2)

Reactgular
Reactgular

Reputation: 54821

Creating an HttpInterceptor that can emit a boolean state of HTTP requests is very tricky. There can be multiple parallel requests happening at the sametime, and it's not as simple as emitting true/false for each HttpRequest. You need to emit true only once when a request starts but emit false when the last request completes.

The easiest way is to keep track of the number of concurrent requests.

Here's the interceptor that I use. It only emits true when a new request is started, and only emits false when all requests have finished.

import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {distinctUntilChanged, finalize, map} from 'rxjs/operators';

class BusySubject extends Subject<number> {
    private _previous: number = 0;

    public next(value: number) {
        this._previous += value;
        if (this._previous < 0) {
            throw new Error('unexpected negative value');
        }
        return super.next(this._previous);
    }
}

@Injectable()
export class BusyInterceptor implements HttpInterceptor {

    private _requests: BusySubject = new BusySubject();

    public get busy(): Observable<boolean> {
        return this._requests.pipe(
            map((requests: number) => Boolean(requests)),
            distinctUntilChanged(),
        );
    }

    public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this._requests.next(1);
        return next.handle(req).pipe(finalize(() => this._requests.next(-1)));
    }
}

p.s. This could properly be done using a scan() operator, but I haven't had time to update the code.

Upvotes: 2

Matt
Matt

Reputation: 2213

You should use a BehaviourSubject instead of a Subject. Subject only emits values when next() is called - if the component is initialised after the service has pushed the value then nothing will ever be received.

BehaviourSubject gets around this by remembering the last emitted value. When your template subscribes it can therefore get the last value and do its comparison.

Upvotes: 1

Related Questions