0

I need to call an api which will return an array of objects. Then I need to loop through all the objects and using an id from each object i need to call another api. Then I am planning to save the data in an array and subscribe, so that I have access to the custom array that contains data from the both api calls. However it is not working for me.

I need combined data from both api calls, however in the resulting array that I am creating, the data from the second api is appearing as observable and when I am trying to use them I am getting undefined error. Any help will highly appreciated.

   this.apiService.getIntakeEvents(90430)
.pipe(mergeMap((res: any)=>{
  const allData =[];
  for(const item of res) {
    let courseInfo = this.apiService.getSpecificCourse(item.courseId)
    allData.push({...item, courseInfo})
  }
  console.log(allData)
  return allData;
}))
.subscribe(res=> console.log(res))
special3220
  • 93
  • 1
  • 10

3 Answers3

1

We need to use a couple of RXJS operators to get what you want.

  • We can use mergeMap to flatten the inner observable
  • We then use combineLatest to get the latest result from each API call
  • We then use map on each individual API call to return the item and it's related courseInfo
this.apiService
      .getIntakeEvents(90430)
      .pipe(
        mergeMap((items) => {
          return items.map((item) =>
            this.apiService.getSpecificCourse(item.courseId).pipe(
              map((courseInfo) => {
                return {
                  item,
                  courseInfo,
                };
              })
            )
          );
        })
      )
      .subscribe((res) => console.log(res));
Dane Brouwer
  • 2,827
  • 1
  • 22
  • 30
  • 1
    `switchMap` doesn't flatten the inner observable, it cancels the current active inner observable when the outer observable emits. In this case as it's an API call (only one emission from the outer observable), there wouldn't be any difference b/n any mapping operators. – ruth Apr 13 '21 at 06:47
1

You'd need to use a combination operator like forkJoin, combineLatest or zip to trigger multiple observables in parallel. Given it's an API call, I'd say forkJoin is a better fit here since it emits only after all the sources are complete. A brief difference b/n them here.

You'd also need to use Array#map to convert the array of objects to an array of HTTP calls. After that you could use RxJS map operator to each source observable to convert back to the object with the modified courseInfo property.

this.apiService.getIntakeEvents(90430).pipe(
  mergeMap((res: any) => 
    forkJoin(
      res.map(item => this.apiService.getSpecificCourse(item.courseId)).pipe(
        map(courseInfo => ({ ...item, courseInfo: courseInfo }))
      )
    )
  )
).subscribe(
  next: (res: any) => {
    console.log(res)
  },
  error: (error: any) => {
    // handle error
  }
);

Update: API calls for multiple properties in object

For updating multiple properties in the object from the outer observable, you might have to use multiple forkJoins wrapped in a outer forkJoin. This is certainly only one way to do it and there might be other better ways.

Example

this.apiService.getIntakeEvents(90430).pipe(
  mergeMap((res: any) => 
    forkJoin({
      courses: forkJoin(res.map(item => this.apiService.getSpecificCourse(item.courseId))),
      names: forkJoin(res.map(item => this.apiService.getSpecificName(item.courseId))),
      ages: forkJoin(res.map(item => this.apiService.getSpecificAge(item.courseId)))
    }).pipe(map(({courses, names, age}) => 
      res.map((item, index) => ({
        ...item,
        courseInfo: courses[index],
        name: names[index],
        age: ages[index]
      })
    ))
  )
).subscribe(
  next: (res: any) => {
    console.log(res)
  },
  error: (error: any) => {
    // handle error
  }
);
ruth
  • 29,535
  • 4
  • 30
  • 57
  • Thanks. It is working. So, say if I have have couple more api calls similar to my second api call in question, I do the same pipe(map(...)) inside the forkJoin? – special3220 Apr 13 '21 at 07:09
  • No the `pipe(map(...)` was only to append the result from the inner API call to the object from the outer API call. For additional API calls, additional `forkJoin`s need to be introduced. – ruth Apr 13 '21 at 07:17
  • In here I am making one api call for all the items. Isnt it possible to to make two or three api calls for all the items, save the return value from the api calls in an array and return that array? res.map((item: any) => this.apiService.getSpecificCourse(item.courseId) – special3220 Apr 13 '21 at 07:24
  • @special3220: The question is vague. Perhaps you could provide what you've tried so far similar to the one in original question. Nevertheless I've updated the answer with an example that might or might not solve your issue. – ruth Apr 13 '21 at 07:27
  • Thanks a lot for your help. I am new to programming and have been stuck with his for couple of days now. You are a savior. – special3220 Apr 13 '21 at 07:43
0

You're going to have to convert

this.apiService.getSpecificCourse(item.courseId);

from an observable to a promise (How you do it depends on your rxjs version which is covered in that link). Once it's converted to a promise you can call .then() which allows you to return an actual value from the callback. An observable only allows you to subscribe which won't allow you to return a value from the callback.

So if you have the latest rxjs. You can do:

let courseInfo = lastValueFrom(this.apiService.getSpecificCourse(item.courseId)).then((info) => info);

which I believe should work

Luke Weaver
  • 271
  • 1
  • 9