Guida Pratica agli Observables in Angular: switchMap, mergeMap, concatMap, exhaustMap

In questo post, esploreremo l'uso di quattro operatori di RxJS : switchMap, mergeMap, concatMap, ed exhaustMap per gestire flussi di dati asincroni complessi in applicazioni Angular.  Scopriremo i punti di forza e le limitazioni di ciascun operatore e vedremo come scegliere quello giusto a seconda dello scenario: che si tratti di garantire l'ordine delle operazioni, considerare solo l'ultimo risultato, o evitare esecuzioni multiple non necessarie.

Contesto

Immaginiamo di dover gestire una serie di richieste HTTP in una applicazione Angular. Supponiamo che ogni richiesta dipenda dalla risposta della precedente. Ad esempio, dobbiamo:

  1. Effettuare una chiamata per ottenere i dettagli di un post.
  2. Utilizzare l'ID dell'autore del post per fare un'altra chiamata e ottenere i dettagli dell'autore.

Obiettivo

L'obiettivo è gestire queste richieste in modo efficiente e mantenere il codice leggibile e manutenibile. Qui entrano in gioco gli higher order observables, che ci permettono di lavorare con flussi di dati asincroni complessi.

Risoluzione

Per risolvere il problema, utilizzeremo quattro operatori principali di RxJS: switchMap, mergeMap, concatMap ed exhaustMap. Ogni operatore ha un comportamento specifico che lo rende adatto a determinati scenari.

mergeMap

Il mergeMap consente di gestire flussi concorrenti di observables. Quando l'osservabile esterno emette un valore, mergeMap sottoscrive immediatamente all'osservabile interno. Se l'osservabile esterno emette rapidamente molti valori, mergeMap sottoscriverà a tutti gli observabili interni contemporaneamente.

Esempio: Immaginiamo un'applicazione di ricerca dove ogni tasto premuto genera una richiesta HTTP. Utilizzando mergeMap, tutte le richieste verranno effettuate senza aspettare che le precedenti siano completate.

this.searchTerms.pipe(
  mergeMap(term => this.http.get(`api/search/${term}`))
).subscribe(results => console.log(results));

Pro: Concorrenza elevata. Contro: L'ordine dei risultati non è garantito.

concatMap

Il concatMap garantisce che le richieste siano eseguite in ordine. Quando l'osservabile esterno emette un valore, concatMap attende che l'osservabile interno precedente sia completato prima di sottoscrivere al successivo.

Esempio: Per garantire che le richieste HTTP siano completate in sequenza, possiamo utilizzare concatMap.

this.searchTerms.pipe(
  concatMap(term => this.http.get(`api/search/${term}`))
).subscribe(results => console.log(results));

Pro: Mantiene l'ordine delle richieste. Contro: Ritardo se gli observables interni sono lenti.

switchMap

Il switchMap annulla la sottoscrizione agli observables interni non completati quando l'osservabile esterno emette un nuovo valore. Questo è utile quando si vuole prendere in considerazione solo l'ultimo valore emesso.

Esempio: In un campo di ricerca, vogliamo considerare solo l'ultima richiesta effettuata dall'utente.

this.searchTerms.pipe(
  switchMap(term => this.http.get(`api/search/${term}`))
).subscribe(results => console.log(results));

Pro: Considera solo l'ultimo valore. Contro: Le richieste precedenti vengono annullate.

exhaustMap

Il exhaustMap ignora nuovi valori emessi dall'osservabile esterno se un'osservabile interno è ancora in corso. Questo è utile per evitare chiamate multiple non necessarie.

Esempio: Per evitare che un utente possa inviare più volte una richiesta di login cliccando ripetutamente sul pulsante, utilizziamo exhaustMap.

this.loginButton.pipe(
  exhaustMap(() => this.authService.login())
).subscribe(results => console.log(results));

Pro: Evita esecuzioni multiple non necessarie. Contro: I nuovi valori emessi vengono ignorati se un'operazione è già in corso.

Risultato

Utilizzando gli higher order observables e i relativi operatori, possiamo gestire flussi di dati asincroni in modo pulito ed efficiente. Ogni operatore ha i suoi punti di forza e debolezze, e la scelta dipende dal caso d'uso specifico.

Recap: Quando Utilizzare Ogni Operatore

  • mergeMap: Utilizzalo quando vuoi gestire flussi di dati concorrenti e non ti preoccupa l'ordine dei risultati. Ideale per applicazioni di ricerca o quando non è importante che le risposte arrivino in ordine.
  • concatMap: Sceglilo quando l'ordine delle operazioni è cruciale. Adatto per processi che devono essere eseguiti sequenzialmente, come l'invio di dati in un ordine specifico al server.
  • switchMap: Perfetto per scenari dove solo l'ultimo valore emesso è rilevante. Ideale per campi di ricerca in tempo reale dove vuoi considerare solo l'ultima richiesta dell'utente.
  • exhaustMap: Utile quando vuoi ignorare nuove emissioni finché l'osservabile corrente non è completato. Ottimo per prevenire azioni multiple, come richieste di login o cancellazioni ripetute.