Search code examples
javascriptangularrxjsrxjs6rxjs-pipeable-operators

Using MergeMap with the array of data received from another observable - RxJs Angular


I have a fetchDrives method which will return an observable which on subscription will return a list of drives

this.fetchDrives(points).subscribe(drives => {
     console.log(drives);
});

Assume The drives array which I got on subscription look some what like this

[ {driveId: 1}, {driveId: 2}, {driveId: 3} ]

Now I need to use the driveId one by one and make three calls ( three because length of the drives array is 3 ) by passing driveId to each api call.I need to pass driveId to the below method one at a time and get the lat and lon and store the result of three calls in an array.

this.getLatLong(driveId).subscribe( res => console.log(res))

The res will contain an object like { lat: 12, lon: 54 }

I don't want to do two subscriptions, is there a way I can use the Rxjs operators and achieve this with one subscription using the result of previous observable, loop through the drives array and make three calls to getLatLong method using mergeMap as the sequence of the calls doesn't matter and store the result of those three calls in an array?

I tried using scan operator to loop through but failed to use it to get the desired output

Thanks for the help in advance :)


Solution

  • If the order of the requests doesn't matter, you could use RxJS forkJoin method to make the calls simultaneously. I've also used switchMap operator to switch the observable once the source observable (this.fetchDrives(points)) emits. Try the following

    locations: any;
    
    this.fetchDrives(points).pipe(
      switchMap((drives) => {
        let source = Object.create(null);
        for (let i = 0; i < drives.length; i++) {
          source[drives[i]['driveId']] = this.getLatLong(drives[i]['driveId']);
        }
        return forkJoin(source);
      })
    ).subscribe(
      response => {
        this.locations = response;
      },
      error => {
        // handle error
      }
    );
    

    Variable locations will be of the form

    // 'driveId': { lat: 12, lon: 54 }
    
    {
      '1': { lat: 12, lon: 54 },
      '2': { lat: 12, lon: 54 },
      '3': { lat: 12, lon: 54 }
    }
    

    Update: array of objects output

    To return an array of objects from forkJoin you could send in an array of observables as the argument. For eg. forkJoin([obs1, obs2, ...]). In the previous case we were sending an object as the argument (forkJoin({'name1': obs1, 'name2': obs2, ...}), so the output will also be an object.

    locations: any = [];
    
    this.fetchDrives(points).pipe(
      switchMap((drives) => {
        return forkJoin(drives.map(drive => this.getLatLong(drive['driveId'])));
      })
    ).subscribe(
      response => {
        this.locations = response;
      },
      error => {
        // handle error
      }
    );
    

    locations will be of the form

    [
      { lat: 12, lon: 54 },
      { lat: 12, lon: 54 },
      { lat: 12, lon: 54 }
    ]