Reputation: 23
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
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
persons
mergeMap
or switchMap
to make a second resquest for person
's pets
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
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