18

I've been looking for a solution all over the web, but couldn't find anything that fits my user case. I'm using the MEAN stack (Angular 6) and I have a registration form. I'm looking for a way to execute multiple HTTP calls to the API and each one is dependent on the returned result from the previous one. I need something that looks like this:

firstPOSTCallToAPI('url', data).pipe(
    result1 => secondPOSTCallToAPI('url', result1)
    result2 => thirdPOSTCallToAPI('url', result2)
    result3 => fourthPOSTCallToAPI('url', result3)
    ....
).subscribe(
    success => { /* display success msg */ },
    errorData => { /* display error msg */ }
);

What combination of RxJS operators do I need to use to achieve this? One possible solution would be to nest multiple subscriptions, but I want to avoid that and do it better with RxJS. Also need to think about error handling.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Andi Aleksandrov
  • 443
  • 2
  • 6
  • 16
  • Is your number of observables defined ? Or does the code has to be dynamic ? – rguerin Nov 30 '18 at 15:49
  • you can use flatMap or switchMap – JEY Nov 30 '18 at 15:50
  • rguerin, I would like to be flexible and not depend on exact number of observables. @JEY I tried using switchMap but i get an error saying "Expected 1-2 arguments, but got 3". Can you give an example, please? – Andi Aleksandrov Nov 30 '18 at 15:55

6 Answers6

58

For calls that depend on previous result you should use concatMap

firstPOSTCallToAPI('url', data).pipe(
    concatMap(result1 => secondPOSTCallToAPI('url', result1))
      concatMap( result2 => thirdPOSTCallToAPI('url', result2))
       concatMap(result3 => fourthPOSTCallToAPI('url', result3))
    ....
).subscribe(
    success => { /* display success msg */ },
    errorData => { /* display error msg */ }
);

if your async method does not depend on return value of the precedent async call you can use

   concat(method(),method2(),method3()).subscribe(console.log)
Fan Cheung
  • 10,745
  • 3
  • 17
  • 39
3

I have faced same problem, this is my solution use pipe and concatMap for array for get sequence data for time period between start and end time.

This is general solution when we have array request.

I share for whom concern.

 let currentReplayData = [];
 let timerange = [[t1, t2], [t3, t4]]; // array of start and end time
 from(timerange).pipe(
      concatMap(time => <Observable<any>>this.dataService.getData(time[0],time[1]))
      ).subscribe(val => {
        //console.log(val)
        this.currentReplayData = this.currentReplayData.concat(val);
      });
Hien Nguyen
  • 24,551
  • 7
  • 52
  • 62
1

MergeMap

is exact what you are looking for

firstPOSTCallToAPI('url', data).pipe(
    mergeMap(result1 => secondPOSTCallToAPI('url', result1)),
    mergeMap(result2 => thirdPOSTCallToAPI('url', result2)),
    mergeMap(result3 => fourthPOSTCallToAPI('url', result3)),
    // ...
).subscribe(
    success => { 
      // here you will get response of LAST request (fourthPOSTCallToAPI)
    },
    errorData => { /* display error msg */ }
);


// I assume that
// secondPOSTCallToAPI, thirdPOSTCallToAPI and fourthPOSTCallToAPI
// returns obserwable eg. return this.http.get<Book>(apiUrl);
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
0
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';

@Injectable()
export class DataService {

  constructor(private http: HttpClient) { }

  public requestDataFromMultipleSources(): Observable<any[]> {
    let response1 = this.http.get(requestUrl1);
    let response2 = this.http.get(requestUrl2);
    let response3 = this.http.get(requestUrl3);
    return Observable.forkJoin([response1, response2, response3]);
  }
}

The above example shows making three http calls, but in a similar way you can request as many http calls as required

    import { Component, OnInit } from '@angular/core';
import { DataService } from "../data.service";

@Component({
    selector: 'app-page',
    templateUrl: './page.component.html',
    styleUrls: ['./page.component.css']
})
export class DemoComponent implements OnInit {
    public responseData1: any;
    public responseData2: any;
    public responseData3: any;

    constructor(private dataService: DataService) {}

    ngOnInit() {
        this.dataService.requestDataFromMultipleSources().subscribe(responseList => {
            this.responseData1 = responseList[0];
            this.responseData2 = responseList[1];
            this.responseData3 = responseList[1];
        });
    }
}
0

Let me show you how to here, assuming I have much of emails I want to deliver an email to sequentially:

sendEmails() {
  this.isLoading = true; 
            const calls = this.emails <<-- assume this contain an array of emails
            .map(email => this.userEmailService.deliver({email: email, userId: 1242}));
            from(calls) <<-- make use of the from.
                .pipe(
                    concatMap(res => res),
                    finalize(() => this.isLoading = false)
                ).subscribe(() => { });
}

I hope this helps.

Alex Onozor
  • 6,841
  • 1
  • 22
  • 26
-4

Try this , Angular provides feature to call multiple API at a time.

forkJoin()

You will get data in array as in same sequence which you call API.

Ex:

forkJoin(request1, request2)
    .subscribe(([response1, response2]) => {

You can find more read

I have also given another answer. Please check this, it may also helps you.

Sachin Shah
  • 4,503
  • 3
  • 23
  • 50
  • 9
    `forkJoin` sends all at once AFAIK, using `concat` ensures the previous one completes – mchl18 Nov 30 '18 at 15:58
  • Thanks for the reply. I've looked into forkJoin but it doesn't look like a working solution since i need request2 to use response data returned from request1. – Andi Aleksandrov Nov 30 '18 at 16:01
  • In that case , forkJoin will not use. I suggest to check second link. It will work in you case. – Sachin Shah Nov 30 '18 at 16:03