'Iterate through Observable object-array and add additional data from another Observable to every object
I have a Angular service returning an array of "cleaningDuty" objects. Inside a duty object, there is a nested object called "currentCleaner" with an id.
[
{
// other cleaningDuty data
currentCleaner: {
id: string;
active: boolean;
};
},
{
// other cleaningDuty data
currentCleaner: {
id: string;
active: boolean;
};
},
{
// other cleaningDuty data
currentCleaner: {
id: string;
active: boolean;
};
}
]
with the help of the currentCleaner.id I want to fetch the user data of the currentCleaner from a UserService dynamically in the same pipe() method and add the returned user data to the cleaningDuty object. Then the object should look like this:
{
// other cleaningDuty data
currentCleaner: {
id: string;
active: boolean;
},
cleanerData: {
name: string;
photoUrl: string;
// other user data
}
},
Unfortunately I just cant get this to work even after investing days into it. I tried almost every combination from forkJoin(), mergeMap() and so on. I know a nested subscribe() method inside the target component would get the job done, but I want to write the best code possible quality-wise. This is my current state of the service method (it adds the user observable instead of the value to the cleaningDuty object):
getAllForRoommates(flatId: string, userId: string) {
return this.firestore
.collection('flats')
.doc(flatId)
.collection('cleaningDuties')
.valueChanges()
.pipe(
mergeMap((duties) => {
let currentCleaners = duties.map((duty) =>
this.userService.getPublicUserById(duty.currentCleaner.id),
);
return forkJoin([currentCleaners]).pipe(
map((users) => {
console.log(users);
duties.forEach((duty, i) => {
console.log(duty);
duty.cleanerInfos = users[i];
});
return duties;
}),
);
}),
);
}
The getPublicUserById() method:
getPublicUserById(id: string) {
return this.firestore.collection('publicUsers').doc(id).valueChanges();
}
Any help would be greatly appreciated!
Solution 1:[1]
- Check if you need
switchMapormergeMap. IMO I'd cancel the existing observable when thevalueChanges()emits. See here for the difference b/n them. - Use
Array#mapwith RxJSforkJoinfunction to trigger the requests in parallel. - Use RxJS
mapoperator with destructuring syntax to add additional properties to existing objects. - Ideally defined types should be used instead of
anytype.
Try the following
getAllForRoommates(flatId: string, userId: string) {
return this.firestore
.collection('flats')
.doc(flatId)
.collection('cleaningDuties')
.valueChanges()
.pipe(
switchMap((duties: any) =>
forkJoin(
duties.map((duty: any) => // <-- `Array#map` method
this.userService.getPublicUserById(duty.currentCleaner.id).pipe(
map((data: any) => ({ // <-- RxJS `map` operator
...duty, // <-- the `currentCleaner` property
cleanerData: data // <-- new `cleanerData` property
}))
)
)
)
)
);
}
Solution 2:[2]
Firstly I would suggest you to improve the typings on your application, that will help you to see the issue more clear. For my answer I will assume you have 3 interfaces defined CurrentCleaner, CleanerData and RoomData wich is just:
interface RoomData {
currentCleaner: CurrentCleaner
cleanerData: CleanerData
}
Then you can write a helper function to retrieve the room data from a current cleaner:
getRoomData$(currentCleaner: CurrentCleaner): Observable<RoomData> {
return this.userService.getPublicUserById(currentCleaner.id).pipe(
map(cleanerData => ({ currentCleaner, cleanerData }))
)
}
As you can see, this functions take a current cleaner, gets the cleaner data for it and maps it into a RoomData object.
Having this next step is to define your main function
getAllForRoommates(flatId: string, userId: string): Observable<RoomData[]> {
return this.firestore
.collection('flats')
.doc(flatId)
.collection('cleaningDuties')
.valueChanges()
.pipe(
mergeMap((duties) => {
// here we map all duties to observables of roomData
let roomsData: Observable<RoomData>[] = duties.map(duty => this.getRoomData$(duty.currentCleaner));
// and use the forkJoin to convert Observable<RoomData>[] to Observable<RoomData[]>
return forkJoin(roomsData)
}),
);
}
I think this way is easy to understand what the code is doing.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Andres Ballester |
