RxJs switchMap with angular HttpClient
Asked Answered
C

3

9

I have a use case whenever a new request is triggered, any http requests that are already in flight should be cancelled / ignored.

For eg:

  • A request (say #2) comes in while the request #1 takes too long to respond / slow network connectivity.
  • In this case #2 gets a very quick response from server then at any point even if the #1 comes back with a response the HTTP response observable should be ignored.
  • The problem i face is, first the component displays response values from request #2 and gets updated again when req #1 completes (this should not happen).

I figured that switchMap cancels obervables / maintains the order in which the observable values are emitted.

excerpt from my service.ts

Obervable.of('myServiceUrl')
             .switchMap(url => this.httpRequest(url) )
              .subscribe( response => {
                   // implementation
                   /** Update an observable with the 
                       with latest changes from response. this will be 
                       displayed in a component as async */
                });


private httpRequest(url) {
        return this.httpClient.get('myUrl', { observe: 'response' });
}

The above implementation doesn't work. Could some one figure out the correct implementation for this usecase.

Competitive answered 28/2, 2018 at 18:17 Comment(2)
How are you using httpResponse$?Marita
I don't need the httpResponse$ as all the implementation is taken care inside subscribe(). updated the code snippet.Competitive
C
4

I have the below code excerpt with which the switchMap implementation was successfull.

class MyClass {
    private domain: string;
    private myServiceUri: subject;
    myData$: Observable<any>;

        constructor(private http: HttpClient) {
            .....
            this.myServiceUri = new Subject();
            this.myServiceUri.switchMap(uri => {
                    return this.http.get(uri , { observe: 'response' })
                            // we have to catch the error else the observable sequence will complete
                            .catch(error => {
                                // handle error scenario
                                return Obervable.empty(); //need this to continue the stream
                            });
                    })
                    .subscribe(response => {
                        this.myData$.next(response);
                    });
        }

        getData(uri: string) {
            this.myServiceUri.next(this.domain + uri); // this will trigger the request     
        }

    }
Competitive answered 12/3, 2018 at 18:50 Comment(1)
What is myData$? I don't see a observable being createdHoar
M
15

It seems like you are creating multiple observables. It's unclear from your example, but it seems like you call Observable.of each time you want to make the request. This creates a new Observable stream each time, so for each subsequent call you get a new stream, and the previous one is not canceled. This is why .switchMap is not working.

If you want .switchMap to cancel the HTTP requests, you need them to use the same observable stream. The source Observable you want to use depends on what exactly is triggering the http requests, but you can manage it yourself using something like Subject.

const makeRequest$ = new Subject();
const myResponse$ = makeRequest$.pipe(switchMap(() => this.service.getStuff()));

You can subscribe to myResponse$ to get the response. Any time you want to trigger a request, you can do makeRequest$.next().

Marita answered 28/2, 2018 at 18:35 Comment(3)
well that makes sense. so with the above implementation the makeRequest$ holds the actual http get request i used. am i right ? let me implement and get back.Competitive
makeRequest$ is just a Subject (Observable) that doesn't really do anything. You just emit to it to cause the observable stream to emit. myResponse$ will have the http response that you want.Marita
I am getting error when use above approach i.e. TypeError: this.myServiceUri.pipe.switchMap is not a function. Also when I use this vscode shows ` import { switchMap } from 'rxjs/operators';` as unused.Winifield
C
4

I have the below code excerpt with which the switchMap implementation was successfull.

class MyClass {
    private domain: string;
    private myServiceUri: subject;
    myData$: Observable<any>;

        constructor(private http: HttpClient) {
            .....
            this.myServiceUri = new Subject();
            this.myServiceUri.switchMap(uri => {
                    return this.http.get(uri , { observe: 'response' })
                            // we have to catch the error else the observable sequence will complete
                            .catch(error => {
                                // handle error scenario
                                return Obervable.empty(); //need this to continue the stream
                            });
                    })
                    .subscribe(response => {
                        this.myData$.next(response);
                    });
        }

        getData(uri: string) {
            this.myServiceUri.next(this.domain + uri); // this will trigger the request     
        }

    }
Competitive answered 12/3, 2018 at 18:50 Comment(1)
What is myData$? I don't see a observable being createdHoar
S
1

An approach which might seem similar to above answer but implementation is different.

In the below example, you can see that the API/http calls get cancelled if called multiple times. The purpose is to avoid API calls resolving which eventually causes browser to respond slowly.(Ex: Typeahead)

switchMap at a time can hold only one observable. Hence, it cancels rest of the API calls.

switch-map.component.ts

import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';

@Component({
    selector: 'switch-map',
    templateUrl: './switch-map.component.html'
})
export class SwitchMapComponent implements OnInit {
    subject: Subject<string> = new Subject()

    constructor(private http: HttpClient) { }

    makeApiCall(): void {
        this.subject.next('https://jsonplaceholder.typicode.com/photos')
    }

    ngOnInit(): void {
        this.subject.pipe(switchMap(url => this.http.get(url)))
        .subscribe(data => console.log('API Response: ', data))
        setInterval(() => {
            document.getElementById('btn')?.click()
        }, 10)
    }
}

switch-map.component.html

<button id="btn" (click)="makeApiCall()">Click</button>
Skirr answered 30/12, 2022 at 22:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.