0

How to chain RXJS Angular httpclient observable api calls whilst retaining initial api response data?

I have two api calls, location and person, the location is an array of locations that contains the person slug. For each location, I would get the slug to call the person api response.

The issue is that I need to save data from the initial location call, like their city, and also save the person's name from person api call.

From what I've read, you can chain api requests by mergeMap, but the resulting value would be the latest api data only.

Location Api Call Data

[
  {
    city: 'mycity',
    person-slug: 'person-x',
  },
  {
    city: 'yourcity',
    person-slug: 'person-y',
  },
]

Api Provider (RXJS Api Call)

private http: HttpClient; // Angular Http Client

const req = this.http.get<Array<any>>('fullLocationApi')
  .pipe(
    // Split up initial array of objects response into stream of objects
    mergeMap(val => {
      return val;
    }),

    mergeMap(val => {

      // Call the second Api to get the person details
      const personSlug = val.person-slug;
      const personUrl = 'fullPersonApiUrl' + personSlug;

      return this.http.get(personUrl);
    }),
    // Combine together the single stream into an array
    // Only saves the city api call data, doesn't save the location city
    reduce((dataArr, data) => {
      return dataArr.concat(data);
    }, []),

Unfortunately, something like saving more data than just the api response in merge map either doesn't work or won't subscribe to the map result.

     // Doesn't work, but would be ideal, that way there's data saved from the old call as well
     mergeMap(val => {

      const personSlug = val.person-slug;          
      const personUrl = 'fullPersonApiUrl' + personSlug;

      return {
        old: val,
        contact: this.http.get(personUrl))
      };

Is there anyway to save the location api call data city to add to the final response array in the reduce function?

One solution would be two api calls, first to save an array of the location data, then have an api call for each contact and modify the existing array of location data.

NeedHelp101
  • 599
  • 1
  • 9
  • 25
  • You can pipe your `this.http.get(personUrl))` like `this.http.get(personUrl)).pipe(map(res => hereCombine(res + locDetails)))` – Akash Aug 19 '20 at 08:03

1 Answers1

2

Instead of triggering each this.http.get('fullPersonApiUrl' + value['person-slug']) individually you could use forkJoin operator to call them in parallel. Then you could use RxJS map operator along with Array map and Object.assign method to return the combined results from first and second request.

Try the following

const req = this.http.get<Array<any>>('fullLocationApi').pipe(
  switchMap(values => {
    return forkJoin(values.map(value => this.http.get('fullPersonApiUrl' + value['person-slug']))).pipe(
      map(contacts => contacts.map((contact, index) => Object.assign(values[index], {contact: contact})))
    );
  })
);

Note:

  • forkJoin will only emit when all the source observables complete.

  • The requests will be in parallel and most browsers have hard limit on number of concurrent requests to a single domain. If you run into this issue, you could look into RxJS bufferCount operator to make buffered parallel requests.

  • The result from forkJoin(values.map(value => this.http.get('fullPersonApiUrl' + value['person-slug']))) will be an array of the form

[
  result from this.http.get('fullPersonApiUrl' + 'person-x'), 
  result from this.http.get('fullPersonApiUrl' + 'person-y'),
  ...
];
  • The final result will be an array of objects of the form
[
  {
    contact: result from this.http.get('fullPersonApiUrl' + 'person-x')
    city: 'mycity',
    person-slug: 'person-x',
  },
  {
    contact: result from this.http.get('fullPersonApiUrl' + 'person-y')
    city: 'yourcity',
    person-slug: 'person-y',
  },
  ...
]
ruth
  • 29,535
  • 4
  • 30
  • 57