I have a foreach loop in which I might need an http request to get some info. I tried using a forkjoin inside the foreach loop to 'make the foreach loop wait for the observables' (in this example there's only one observable, but I will have more in reality...). BUT with this code, the foreach each keeps running without waiting for the observables to complete, and I cannot find any solution to prevent this.
So your help is much appreciated!!!
...
let wishes: Wish[] = [];
response.wishes.forEach((wish) => {
let newWish : Wish = new Wish( wish._id, wish.title, wish.price );
let personObservables: Observable<any>[] = [];
if (wish.reservation){
personObservables.push(this.peopleService.getPersonById(wish.reservation.reservedBy));
} else {
personObservables.push(of(null));
}
forkJoin(personObservables).subscribe( ([reservedBy]) => {
if (reservedBy) {
newWish.reservation = {
...wish.reservation,
reservedBy
};
}
wishes.push(newWish);
} );
});
...
EDIT: Full blown working solution without foreach loop. It's much easier to use map operator in pipe and map function on array. I learned that it's easier to split this kind of logic over several operators instead of trying to fix it all in 1 map operator...
public getWishlist ( receiver : Person) : Observable<Wish[]> {
return this.http$.get<IWishlistResponse[]>(
environment.apiUrl + 'wishlist/' + receiver.id
).pipe(
// Create wish instances from each wish in API response and save reservation for later use
map( wishlistResponse =>
wishlistResponse[0].wishes.map(wish => ({
wish: this.createWishInstanceFromResponse(wish, receiver),
reservation: wish.reservation
}))),
// For each wish with reservation: get person info for 'reservedBy' id
map( wishesAndReservationObjects => wishesAndReservationObjects.map( ({wish, reservation}) =>
!reservation ?
of(wish) :
this.peopleService.getPersonById(reservation.reservedBy)
.pipe(
map ( reservedBy => {
if (reservedBy) wish.reservation = {
...reservation,
reservedBy: new Person(reservedBy.id, reservedBy.firstName, reservedBy.lastName)
}
return wish;
})
)
)),
// forkJoin all observables, so the result is an array of all the wishes
switchMap(reservedByObservables => reservedByObservables.length !== 0 ? forkJoin(reservedByObservables) : of(<Wish[]>[])), //https://stackoverflow.com/questions/41723541/rxjs-switchmap-not-emitting-value-if-the-input-observable-is-empty-array
// Call method on each wish (with or without reservation) to set user flags in each instance (must be done after reservedBy is added)
map ( wishes => wishes.map( wish => {
wish.setUserIsFlags(this.userService.currentUser);
return wish;
})),
// For each wish: get state via API call
map ( wishesWithoutState => wishesWithoutState.map( wishWithoutState =>
this.http$.get<wishStatus>(environment.apiUrl + 'wish/' + wishWithoutState.id + '/state')
.pipe(
catchError(() => of(null)),
map( state => {
wishWithoutState.status = state;
return wishWithoutState;
})
)
)),
// Combine all stateObservables into 1 array
switchMap(stateObservables => stateObservables.length !== 0 ? forkJoin(stateObservables) : of(<Wish[]>[]))
)
}
private createWishInstanceFromResponse ( wishResponse : IWishResponse, receiver: Person ) : Wish {
let wish : Wish = new Wish (
wishResponse._id,
wishResponse.title,
wishResponse.price,
...
);
return wish;
}