Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 25, 2021 04:41 pm GMT

Angular: Spinner Interceptor

Basically, I needed a means to provide a spinner that blocked functionality while API calls were in-flight. Additionally, I wanted to take into account that there could be more than one API request in-flight, at one time.

Repository

A Failed Attempt

My first attempt was to use an interceptor service that contained a BehaviorSubject (Observable). I set it up to maintain a counter and set the observable's value to true if there were more than zero (0) requests in-flight.

Through heavy use of the console.log functionality, I came to realize that the interceptor was not always active, even though I was following proper singleton patterns.

Working Version

The second attempt went more smoothly.

I had a second service (a handler) that maintained the counts and the BehaviorSubject. This one worked "like a charm."

Spinner Interceptor Service

spinner-interceptor.service.ts

import { Injectable } from '@angular/core';import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';import { Observable } from 'rxjs';import { finalize } from 'rxjs/operators';import { SpinnerHandlerService } from './spinner-handler.service';@Injectable()export class SpinnerInterceptorService implements HttpInterceptor {  constructor(    public spinnerHandler: SpinnerHandlerService  ) {}  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {    this.spinnerHandler.handleRequest('plus');    return next      .handle(request)      .pipe(        finalize(this.finalize.bind(this))      );  }  finalize = (): void => this.spinnerHandler.handleRequest();}

Unit Tests ...

spinner-interceptor.service.spec.ts

import { TestBed } from '@angular/core/testing';import { of } from 'rxjs';import { SpinnerInterceptorService } from './spinner-interceptor.service';import { SpinnerHandlerService } from './spinner-handler.service';describe('SpinnerInterceptorInterceptor', () => {  let service: SpinnerInterceptorService;  beforeEach(async () => {    TestBed.configureTestingModule({      providers: [        SpinnerInterceptorService,        SpinnerHandlerService      ]    }).compileComponents();  });  beforeEach(() => {    service = TestBed.inject(SpinnerInterceptorService);  });  it('should be created', () => {    expect(service).toBeTruthy();  });  it('expects "intercept" to fire handleRequest', (done: DoneFn) => {    const handler: any = {      handle: () => {        return of(true);      }    };    const request: any = {      urlWithParams: '/api',      clone: () => {        return {};      }    };    spyOn(service.spinnerHandler, 'handleRequest').and.stub();    service.intercept(request, handler).subscribe(response => {      expect(response).toBeTruthy();      expect(service.spinnerHandler.handleRequest).toHaveBeenCalled();      done();    });  });  it('expects "finalize" to fire handleRequest', () => {    spyOn(service.spinnerHandler, 'handleRequest').and.stub();    service.finalize();    expect(service.spinnerHandler.handleRequest).toHaveBeenCalled();  });});

Spinner Handler Service

spinner-handler.service.ts

import { Injectable } from '@angular/core';import { BehaviorSubject } from 'rxjs';@Injectable({  providedIn: 'root'})export class SpinnerHandlerService {  public numberOfRequests: number = 0;  public showSpinner: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);  handleRequest = (state: string = 'minus'): void => {    this.numberOfRequests = (state === 'plus') ? this.numberOfRequests + 1 : this.numberOfRequests - 1;    this.showSpinner.next(this.numberOfRequests > 0);  };}

Spinner Component

spinner.component.ts

import { Component } from '@angular/core';import { SpinnerHandlerService } from '@core/services/spinner-handler.service';@Component({  selector: 'spinner',  templateUrl: './spinner.component.html',  styleUrls: ['./spinner.component.scss']})export class SpinnerComponent {  spinnerActive: boolean = true;  constructor(    public spinnerHandler: SpinnerHandlerService  ) {    this.spinnerHandler.showSpinner.subscribe(this.showSpinner.bind(this));  }  showSpinner = (state: boolean): void => {    this.spinnerActive = state;  };}

spinner.component.html

<div class="spinner-container" *ngIf="spinnerActive">  <mat-spinner></mat-spinner></div>

spinner.component.scss

.spinner-container {  background-color: rgba(0,0,0, 0.1);  position: fixed;  left: 0;  top: 0;  height: 100vh;  width: 100vw;  display: flex;  align-items: center;  justify-content: center;  z-index: 10000}

One More Thing

Don't forget to add the interceptor service into app.module.ts ...

providers: [  { provide: HTTP_INTERCEPTORS, useClass: SpinnerInterceptorService, multi: true }],

Repository

Conclusion

This pattern is a reasonable one and the observable can be used in a variety of scenarios.


Original Link: https://dev.to/rfornal/angular-spinner-interceptor-522i

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To