There are several ways you can do something like this with rxJs. They vary in the way they manage the parallelism of the calls to the second endpoint, i.e. the endpoint that returns details per each item.
MAXIMUM PARALLELISM
Let's assume you receive 10 items in the array from the first endpoint and you want to run all the 10 calls to the second endpoint in parallel. In this case you can use the forkJoin operator like this
testMethod() {
this.testService.getItemList().pipe(
// transform the array of items into an array of Observables
// note that the outer map is the rxJs operator while the inner one is the
// the javascript array method
// note also that we return the array of items since we will need it later
map(items => [items.map(item =>
this.testService.getItemDetails(item.id)), items]
),
// then switch to a new Observable which will emit when all of the calls
// to the second endpoint have returned
switchMap(([arrayOfObs, items]) => forkJoin(arrayOfObs).pipe(
// return both the results of the calls to the second endpoint and the
// original array of items
map(itemDetails => [itemDetails, items])
)),
// finally augment the original array of items with the detail info
// as in your original code but with no subscription any more
tap(([itemDetails, items]) => {
items.forEach((item, i) => {
this.itemArr.push(item);
this.itemArr[i]['details'] = itemDetails[i];
});
})
).subscribe();
}
NO PARALLELISM
If you do want to execute the calls to the second endpoint sequentially, you can use the concatMap operator like this
testMethod() {
this.testService.getItemList().pipe(
// transform the array of items into a new stream which notifies sequentially
// each item in the array - we use the from rxJs function to create the new stream (i.e. the new Observable)
switchMap(items => from(items)),
// then concatenate the calls to the second endpoint with concatMap
concatMap(item => this.testService.getItemDetails(item.id).pipe(
// return the original item with its details
map(itemDetail => {
item['details'] = itemDetail;
return item
})
)),
// finally gather all items into an array
toArray()
).subscribe();
}
CONTROLLED CONCURRENCY
If you want a certail level of parallelism, e.g. 5 calls in parallel at max, you can substitute contactMap with mergeMap specifying the level of concurrency using the second parameter of mergeMap.