Nahq
Nahq

Reputation: 23

How to merge data from two different api endpoints for each element returned?

I have a problem that I don't know how to resolve. I'm going to simplify it so that I can explain it better.

I have one API endpoint that returns me a list of persons, and other API endpoint where i can ask for the pets that a person has using the person ID (something like: api/pets/:person_id/).

The api for query the persons returns me an array that looks something like this:

[
    { 
        person_id: 123, 
        name: "John Abc"
    }, 
    ...
]

What I need is to add the array of pets for each element/person that the api of persons returns me, so that I end with something like this:

[
    { 
        person_id: 123, 
        name: "John Abc",
        pets: [
            { pet_id: 5, pet_name: 'doggy' },
            { pet_id: 7, pet_name: 'foobar' },
            ...
        ]
    }, 
    ...
]

I'm new to RxJs, I have read about different operators but this is something more complex that I have done previously. Any help will be appreciated.

Upvotes: 0

Views: 1374

Answers (2)

Owen Kelvin
Owen Kelvin

Reputation: 15083

It depends on how you receive the data from your backend.

Assuming that the data for pets is received differently from that of persons


import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';

interface IPet {
  pet_id: number;
  pet_name: string;
}
interface IPerson {
  person_id: number;
  name: string;
  pets?: IPet[]
}

export MyComponentClass {
  pets$: Observable<IPet[]> = this.httpClient.get<IPet[]>('/pets');
  persons$: Observable<IPerson[]> = this.httpClient.get<IPerson[]>('/persons');
  personsWithPets$: IPerson = combineLatest([this.persons$, this.pets$]).pipe(
     map(([persons, pets]) => this.persons.map(person => 
       ({...person, pets: pets.filter(({person_id}) =>pet.person_id === person_id )}))
     )
  )
  persons: IPerson[];

  ngOnInit () {
    personsWithPets$.subscribe({
      next: persons => this.persons = persons
    })
  }
}

Edit 1

For your use case, you can follow the below steps

  1. Get all persons
  2. Use higher order observable such as mergeMap or switchMap to make a second resquest for person's pets
  3. Combine results of the two

  persons$ = this.http.get<any>('/persons').pipe(
    mergeMap(persons => forkJoin([
      of(persons),
      forkJoin(persons.map(person => 
      this.http.get(`persons/${person.id}/pets`)))
    ])),
    map(([persons, pets]) => persons.map((person, key) => ({
      ...person, pets:pets[key]
    })))
  )

Below is a demo on stackblitz

I am using async pipe to subscribe

Upvotes: 1

Mrk Sef
Mrk Sef

Reputation: 8032

You can map the response of IPeople into a list of Pet calls that mix the two together.

That might look like this:

this.httpClient.get<IPerson[]>('/persons').pipe(
  map(people => people.map(person => 
    this.httpClient.get<IPet[]>(`/pets/${person.id}`).pipe(
      map(pets => ({
        ...person,
        pets
      }))
    )
  )),
  switchMap(calls => forkJoin(calls))
).subscribe(console.log);

You can also combine the map and switchMap into one call like this:

this.httpClient.get<IPerson[]>('/persons').pipe(
  switchMap(people => forkJoin(
    people.map(person => 
      this.httpClient.get<IPet[]>(`/pets/${person.id}`).pipe(
        map(pets => ({
          ...person,
          pets
        }))
      )
    )
  ))
).subscribe(console.log);

You can generally define smaller pieces of your transformation separately to make things a bit easier to follow/read. Up to you.

/***
 * Returns an observable that add an array of pets to the given person
 ***/
function addPets(person: IPerson): Observable<IPerson>{
  return this.httpClient.get<IPet[]>(`/pets/${person.id}`).pipe(
    map(pets => ({
      ...person,
      pets
    }))
  );
}

/***
 * Print an array of people (with their pets included) to the console
 ***/
this.httpClient.get<IPerson[]>('/persons').pipe(
  switchMap(people => forkJoin(
    people.map(person => addPets(person))
  ))
).subscribe(console.log);

Upvotes: 1

Related Questions