Angular: simulare la latenza della rete con un Http Interceptor

Home/angular, code, italian, javascript/Angular: simulare la latenza della rete con un Http Interceptor

Angular: simulare la latenza della rete con un Http Interceptor

In questo articolo viene descritto brevemente il codice per la creazione di un semplice interceptor da utilizzare in Angular 6+ allo scopo di intercettare tutte le richieste HTTP e simulare la latenza della rete. Sarà inoltre gestita una proprietà pending da utilizzare nella UI per visualizzare un’icona animata “spinner”, un messaggio o applicare un colore differente alla user interface, durante le fasi di caricamento.

Perchè

Durante lo sviluppo di una Single Page Application risulta spesso necessario verificare che la UI si comporti in modo adeguato nelle fasi di caricamento di una richiesta HTTP, ovvero durante il periodo in cui si attende la risposta da un server,  mostrando ad esempio uno spinner, disabilitando alcuni controlli e così via.
Tuttavia, lavorando in locale, i tempi di risposta sono immediati e, di conseguenza, non ho modo di verificare se tali procedure funzionino correttamente.

Perché non usare quindi i DevTools di Chrome, che permettono di simulare la latenza della rete (vedi immagine sotto), invece di creare un interceptor?
Semplice, perché la mia necessità è esclusivamente quella di ritardare le richieste XHR con una certa precisione (ad es. 1 secondo) e potrei anche decidere di rallentare solo quelle che hanno un determinato url (ad es. /users).

Inoltre, la creazione di questo interceptor rappresenta la base di partenza del prossimo articolo in cui descrivo l’utilizzo dell’istruzione useFactory, utile per passare parametri ad un servizio in fase di configurazione del provider.

Latency Interceptor

Per creare un interceptor è necessario scrivere una classe che estende HttpInterceptor e implementare il metodo intercept che sarà invocato ad ogni richiesta Http.
Al fine di simulare la latenza utilizzo l’operatore delay fornito dalla libreria RXJS (versione 6.x).

import { 
 HttpEvent, HttpHandler, HttpInterceptor, HttpRequest 
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable()
export class LatencyInterceptor implements HttpInterceptor {

  intercept( 
    req: HttpRequest, next: HttpHandler
  ): Observable {
    return next.handle(req)
      .pipe(
        delay(1000)
      )
  }
}

Per utilizzare questo interceptor sarà sufficiente registrarlo tra i providers di un ngModule, preferibilmente il modulo root:

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

L’opzione multi indica che HTTP_INTERCEPTORS è un multi-provider. In altre parole abbiamo la possibilità di fornire più di una dipendenza allo stesso token.
In Angular sono disponibili altri multi-provider come, ad esempio, APP_INITIALIZER e NG_VALIDATORS.
Infatti, è possibile includere più di un interceptor nella vostra applicazione, aggiungere diversi form custom validators o servizi da avviare in fase di inizializzazione.

Loader

Nell’esempio seguente integriamo una nuova classe Loader che espone semplicemente una proprietà pending.
La soluzione più corretta sarebbe quella di esporre tale proprietà tramite dei getter/setter. Inoltre potrebbe essere un Observable ed essere definita in un file separato. Tuttavia, allo scopo di semplificare il codice, al momento ho inserito la classe proprio sopra il nostro interceptor ed espone una proprietà pubblica pending.

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

@Injectable({ providedIn: 'root' })
export class Loader {
  pending = false;
}

@Injectable()
export class LatencyInterceptor implements HttpInterceptor {

  constructor(private loader: Loader) {}

  intercept( 
    req: HttpRequest, next: HttpHandler
  ): Observable {
    return next.handle(req)
      .pipe(
        tap(() => this.loader.pending = true),
        delay(1000), // simulate latency
        finalize(() => {
          this.loader.pending = false;
        })
      )
  }
}

Nel codice precedente si notano alcune novità rispetto alla precedente versione:

  • nel costruttore dell’interceptor inietto la classe Loader
  • utilizzo l’operatore tap per impostare a true il valore della proprietà pending all’inizio di ogni nuova richiesta
  • utilizzo l’operatore finalize, invocato sia nel caso di successo che fallimento della richiesta HTTP, per impostare la proprietà pending a false
  • Da notare che la classe Loader utilizza providedIn per definire in quale modulo creare l’injector, in questo caso il modulo di root. providedIn è una novità introdotta in Angular 6. Se utilizzate Angular 5 o precedenti non sarà disponibile e dovrete semplicemente evitare questa opzione e registrare “manualmente” l’injector nel decoratore del modulo root utilizzando l’attributo providers.

L’utilizzo di questo interceptor è identico al precedente esempio ma ora avrete la possibilità di iniettare ovunque il servizio Loader

constructor(public loader: Loader) {}

e di utilizzare la proprietà pending per mostrare un’icona spinner, applicare un colore o un visualizzare un messaggio durante le operazioni di caricamento:

<i class="spinner *ngIf="loader.pending"></i>

Configurare la proprietà delay

Questo interceptor potrebbe far parte di una libreria di componenti e servizi riutilizzabili da importare in diversi progetti.
Non sarebbe quindi utile se potessimo configurarlo per supportare differenti comportamenti, tra i quali, ad esempio un differente valore per il delay, che attualmente è invece hard-coded (a 1000ms) nella classe dell’interceptor?
Questo argomento sarà affrontato nel prossimo articolo, Angular: configurare le dipendenze utilizzando useFactory, in cui vi mostrerò l’utilizzo dell’istruzione useFactory 😉

2018-07-07T16:34:52+00:00 luglio 7th, 2018|angular, code, italian, javascript|

Leave A Comment