Home > Blockchain >  unable to execute forkJoin without nested subscribe()
unable to execute forkJoin without nested subscribe()

Time:11-21

I have this situation where after calling MainDataSourceGet() in the code below, I need to call a second service - stateGet$() - to get additional data for each item of type "type1".

My question is: is subscribing to forkJoin a bad practice? If I dont do it, the the second api will not be consumed and the dictionary with the statusResults will be empty.

I added some comments to the code, hope it makes sense.

const stateGet$ = (x: model) => {
    //need to do this split as part of the logic
  let compositeVals = x.ref.split(':');
  let id = compositeVals[0];
  let propertySet = compositeVals[1];
  return this.myService.apiStatesGet$({id, propertySet});
}

return this.myService
   //call first (main) api
  .MainDataSourceGet()
  .pipe(
    take(1),
    
    //ignore all if there is no result
    filter(ds => ds.Data && ds.Data.length > 0),
    
    //from this point I want to consume "stateGet$" on a item by item basis
    //with the sole purpose of calling this.addToStore() passing statuses as well
    switchMap((data) => {
      //filter 'type1 items'
      let type1Data = data.filter(k => k.ref.includes('type1'));
      //this dic will hold statuses
      var dicIdStatus: { [id: string] : boolean; } = {};
      //second api call
      forkJoin(type1Data.map(v => stateGet$(v)
        .pipe(
          map(statusResult => {
            dicIdStatus[statusResult.id] = statusResult.isActive;
          }),
      ))
      )
      //without this subscribe() the pipe > map action will not run, and the dictionary will be empty
      .subscribe();
      return of({data, dicIdStatus});
    }),
    tap(data => {
      let dataSources = data.data;
      let type1Data = dataSources.filter(k => k.ref.includes('type1'));
      //finally do the thing with both items and statuses dictionary
      this.addToStore(type1Data, data.dicIdStatus);
    }),
    map(() => { return someValue; })
    );

CodePudding user response:

Generally, the way to do this is to let switchMap subscribe for you. It will subscribe to the observable you return.

Using a switchMap to return return of({data, dicIdStatus}); pretty much never makes sense. If you're just immediately emitting a single value, you may as well just use map.

These two are semantically pretty much the same:

switchMap(v => of({named: v}))
map(v => ({named: v}))

Instead if you return the observable that you'd like to subscribe to, you solve this problem. I can't test this for you, but that may look something like this:

const stateGet$ = (x: model) => {
  //need to do this split as part of the logic
  let compositeVals = x.ref.split(':');
  let id = compositeVals[0];
  let propertySet = compositeVals[1];
  return this.myService.apiStatesGet$({id, propertySet});
}

//call first (main) api
return this.myService.MainDataSourceGet().pipe(
  take(1),
  
  //ignore all if there is no result
  filter(ds => ds.Data && ds.Data.length > 0),
  
  //filter 'type1 items'
  map(data => data.filter(k => k.ref.includes('type1'))),

  //from this point I want to consume "stateGet$" on a item by item basis
  //with the sole purpose of calling this.addToStore() passing statuses as well
  switchMap(type1Data => 
    
    //second api call
    forkJoin(
      type1Data.map(stateGet$)
    ).pipe(

      map(statusResults => {
        //this dic will hold statuses
        let dicIdStatus: { [id: string] : boolean; } = {};
        statusResults.forEach(statusResult => 
          dicIdStatus[statusResult.id] = statusResult.isActiv
        )
        return dicIdStatus;
      }),

      map(dicIdStatus => ({type1Data, dicIdStatus}))
    )
  ),

  tap(({type1Data, dicIdStatus}) => 
    //finally do the thing with both items and statuses dictionary
    this.addToStore(type1Data, dicIdStatus)
  ),

  map(() => someValue)

);
  • Related