0

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.

1

2 Answers 2

1

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);
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks!! Perfectly explained also.
1

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

3 Comments

I would switch out combineLatest for forkJoin :)
First of all thank you. But I cannot resolve my problem that way because as I stated in my question the endpoint for the pets requires the person_id in the url as a parameter: api/pets/:person_id/, ie: api/pets/123/. That's why I have such a hard time figuring this out.
yes @MrkSef that is a great advice, I am updating the solution

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.