简体   繁体   中英

Return a merged object composed by two different Observables with dependencies with RxJS

I'm going to explain the context I have before explain the problem design a service with Angular and RxJS.

I have one object with this model:

{
  id: string;
  title: string;
  items: number[];
}

I obtain each object of this type (called "element") through GET /element/:id

Each element has an items: number[] that contains an array of numbers and each number has an URL so I do a new GET /element/:id/:itemNumber for each number and return the items detail. That item detail model is like:

{
  idItem: string;
  title: string;
}

So in my service I want to serve to the components a method that it will return an Observable, it will obtain an array of objects, where each object has the element model with one addition property that will be an array of its detailed items. So, I'm going to show what I have:

The service:

getElement(id: string): any {
  return this.http.get<Element>(this.basePath + `/element/${id}`).pipe(
    map(element => this.getAllItemsDetail(element))
}

getAllItemsDetail(element: Element): any {
  return of(...element.items).pipe(
    map(val => this.getItemDetail(element.id, val)),
    concatAll()
  );
}

My problem is, I'm not understanding how I can, in RxJS, after the map(element => this.getAllItemsDetail(element)) in the getElement method, merge the items array returned by it and the previous element I have before the map that has the element object. I need to add "anything" after that map to compute an Object.Assign to merge the both objects.

EDIT

With the answer of @chiril.sarajiu, he gives me more than one clue, with switchMap I can map the observable ( map is used for objects, "do a compute with the result object"). So my code, it's not the most pretty version I know..., I will try on it, but it works. So the final code looks like this:

getElement(id: string): any {
  return this.http.get<Element>(this.basePath + `/element/${id}`).pipe(
    switchMap(element => this.getAllItemsDetail(element)),
    map(result => { return Object.assign({}, result.element, {itemsDetail: result.items})})
  );
}

getAllItemsDetail(element: Element): any {
  const results$ = [];

  for (let i = 0; i < element.items.length; i++) {
    results$.push(this.getItemDetail(element.id, element.items[i]));
  }

  return zip(...results$).pipe(
    map(items => { return Object.assign({}, { element, items })})
  );
}

Be aware of the possibility about zip will not emit values if the results$ is empty, in that case you can add of(undefined) to the results$ array and after take into account that items will be [undefined]

To map another observable you should use switchMap function instead of map So

getElement(id: string): any {
  return this.http.get<Element>(this.basePath + `/element/${id}`).pipe(
    switchMap(element => this.getAllItemsDetail(element))
}

UPDATE

Consider this medium article

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM