Question
Looping through Observable and mapping result cuncurrently
I have a NgRx selector data$: Observable<Data[]>
which contains array of objects. Inside my component I need to go through each data item and make two HTTP calls. One is conditional (based on data from another observable isConditionTrue$: Observable<boolean>
).
The data is being subscribed inside the table via async
pipe. Additional HTTP calls will map some of the data
attributes.
HTTP calls should be performed concurrently.
For instance:
- The Component initialization, data$ is being subscribed inside the table, value is empty array []
data$
emits the first value, an array has length 5, 5 items will be displayed in the table immediately- Loop through 5 items
- Each item triggers
httpCallA
- Some item triggers
httpCallB
ifisConditionTrue$
returnstrue
httpCallB
from item no.3 is completed- Table item no.3 will be rerendered
httpCallA
from item no.2 is completed- Table item no.2 is rerendered and so on.
If some of the request fails, other requests should continue.
I have tried pushing result of the HTTP call into results array, however it is not a good solution (as it creates more results than we need).
I had also an idea to find object from data by ID after each HTTP call is completed, but maybe there is a better solution.
Whole code feels very complicated and I can imagine that somebody else could have hard time during reading it.
vm$ = combineLatest([data$, isConditionTrue$]).pipe(
switchMap(([data, isConditionTrue]) => {
// no data
if (!data?.length) {
return of({ data: [], isConditionTrue });
}
const result = [];
// loop through data
return from(data).pipe(
// create concurent HTTP calls
mergeMap((dataItem) => {
// combine multiple http request
return merge(
// first http call (should be performed everytime)
httpCallA(dataItem.id).pipe(
map((response) => {
if (!response) {
return dataItem;
}
return {
...response,
...dataItem,
};
}),
catchError((error) => {
console.error(error);
return of(dataItem);
}),
),
// second http request (conditional)
of(dataItem).pipe(
filter(() => isConditionTrue), // only perform when condition is true
mergeMap(() => {
// if dataItem is of specific type call HTTP call
if (dataItem.conditionA) {
return httpCallB(dataItem.id).pipe(
map((res) => ({ ...res, dataItem })),
catchError((error) => {
console.error(error);
return of(dataItem);
}),
);
}
// otherwise
return httpCallC(dataItem.id).pipe(
map((res) => ({ ...res, dataItem })),
catchError((error) => {
console.error(error);
return of(dataItem);
}),
);
}),
),
);
}),
tap((value) => result.push(value)),
map(() => ({ data: result, isConditionTrue })),
);
}),
startWith({ data: [], isConditionTrue: false }),
);
Any help is much appreciated. Thank you!