An Interest In:
Web News this Week
- April 19, 2024
- April 18, 2024
- April 17, 2024
- April 16, 2024
- April 15, 2024
- April 14, 2024
- April 13, 2024
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
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To