rxjsrxjs-observablesconcatmap

how to make concatMap nested


I need to obtain a series of data for each of the dates of origin that I have in an array

array

and I need to obtain them in order so I use concatMap to go through my observable of dates and when I obtain the first group of values everything goes fine

of(...this.etiquetasEjeX)
  .pipe(
    concatMap(item=>
        this.dataService.getGastadoRealizadoEnMesYAño(this.proyectoId,
        getMonthNumber(item.slice(0,item.length-4)),
        +item.slice(-4),
        this.acumular)
        ),
    toArray()
  )
  .subscribe(item=>{
    this.gastadoRealizado=item;
    console.log('this.gastadoRealizado: ', this.gastadoRealizado);
  });

I have my this.gastoRealizado array

gastorealizado

but I need to do 3 More calls to the backend to obtain a total of 4 arrays that then feed a graph and I don't know how to add more calls in this established order

Any idea, please?

Thanks


Solution

  • Not sure if this answers your question (couldn't post a comment to ask for specifics). But assuming the other back end calls also depend only on an element from this.etiquetasEjeX you could use the zip operator. However regarding the usage of concatMap the four request would be performed simultaneously per item. If you were restricted to one api call at a time this solution would need some adjustments.

    import { of, zip } from 'rxjs';
    import { concatMap, toArray } from 'rxjs/operators';
    
    //...
    
    of(...this.etiquetasEjeX)
      .pipe(
        concatMap(item=>
            zip(
                this.dataService.getGastadoRealizadoEnMesYAño(...),
                this.dataService.getB...,
                this.dataService.getC...,
                this.dataService.getD...,
            ),
        toArray(),
      )
      .subscribe((arr: [ItemA, ItemB, ItemC, ItemD][])=> {
        //...
      });
    

    Edit:

    okay in your comment you mentioned that a subsequent request depends on the result of a prior request. Nesting concatMap operations like your initially requested would be a little messy as you can see in the following example:

    of(...this.etiquetasEjeX)
      .pipe(
        concatMap(item=>
          this.dataService.getGastadoRealizadoEnMesYAño(...).pipe(
            concatMap(itemA =>
              this.dataService.getB(itemA).pipe(
                concatMap(itemB =>
                  this.dataService.getC(itemB).pipe(
                    concatMap(itemC =>
                      this.dataService.getD(itemC).pipe(
                        map(itemD =>
                          // every combination of items would be available here
                          this.getGraph(item, itemA, itemB, itemC, itemD)
                        )
                      )
                    )
                  )
                )
              )
            )
          )
        ),
        toArray(),
      )
      .subscribe(graphs => {
        // graphs array contains elements that were returned by this.getGraph 
      });
    

    But the same operations could also be called sequentially without losing the intermediate results that you will need for feeding your graph:

    of(...this.etiquetasEjeX)
      .pipe(
        concatMap(item=> combineLatest(
          of(item),
          this.dataService.getA(item, ...),
        )),
        concatMap(([item, itemA]) =>
          this.dataService.getB(itemA, ...)
    combineLatest(
          of([item, itemA]), // it's important that the elements are enclosed inside an array here otherwise the of operator fires twice and doesn't have the proper effect
          ,
        )),
        concatMap(([prevItems, itemB]) => combineLatest(
          of([...prevItems, itemB]),
          this.dataService.getC(itemB, ...),
        )),
        concatMap(([prevItems, itemC]) => combineLatest(
          of([...prevItems, itemC]),
          this.dataService.getD(itemC, ...),
        )),
        map(([item, itemA, itemB, itemC, itemD]) =>
          this.getGraph(item, itemA, itemB, itemC, itemD)
        ),
        toArray(),
      )
      .subscribe(graphs => {
        // update the state of your object eg.
        this.myGraphs = graphs;
      });
    

    And I noticed that you are using toArray. That means your observable won't provide you with any intermediate results until all your api calls have been finished. Depending on the size of the resulting arrays provided by your api the given solution might be quite time and memory consuming.