Samuel
Samuel

Reputation: 1426

Using MergeMap with SwitchMap to result from NgRx Store

I have 3 selectors:

  1. getUserInfo => to fetch acnt details (sample output : {acntId: 'A1'})
  2. getAllDepartments => to fetch list of all dept Ids (sample Output: ['d1','d2'] )
  3. getAllDeptManagers => to get list of dept managers for each Dept Id.

Now, I have written below code:

 this.store
        .select(getUserInfo)
        .pipe(
          switchMap((res) => this.store.select(getAllDepartments, { account: res.acntId})),
          mergeMap(deptId => this.store.select(getDepartmentManagers,{departmentId: deptId }))
        )
        .subscribe((depts) => {
          console.log(depts);
        })
    );

As per my understanding, mergeMap takes array and accordingly call a function and flatten the array of observable which is returned.

I am getting ['d1','d2'] in every call to selector getAllDeptManagers . what I am expecting is d1 and then d2 and so on, and then get all response in one go as depts of console.

Please help

Upvotes: 1

Views: 935

Answers (2)

Mrk Sef
Mrk Sef

Reputation: 8062

The simplest way to achieve what you want is to map your array into a stream. The first switchMap takes a value and maps it onto a stream. That stream emits an array, so you just need one more mergeMap. Like this:

this.store.select(getUserInfo).pipe(
  switchMap(res => this.store.select(getAllDepartments, { account: res.acntId})),
  mergeMap(depIdArr => depIdArr), // <- The extra bit
  map(deptId => this.store.select(getDepartmentManagers,{departmentId: deptId })),
  // Really, your source observables should be competeing, 
  // but if they don't take(1) should ensure that they do.
  mergeMap(depMan$ => depMan$.pipe(take(1))),
  toArray()
).subscribe(console.log);

That mergeMap looks funny (the extra bit), but if you return an array it gets converted into a stream. It's semantically the same as mergeMap(depIdArr => from(depIdArr)) but it's a bit more performant.

If turning an array into a stream and back into an array is too much, you can combine those steps with zip(), combineLatest(), or what might be best in this case: forkJoin()

this.store.select(getUserInfo).pipe(
  switchMap(res => this.store.select(getAllDepartments, { account: res.acntId})),
  map(deptIdArr => deptIdArr.map(
    deptId => this.store.select(
      getDepartmentManagers,{departmentId: deptId }
    )
  )),
  // Really, your source observables should be completing, 
  // but if they don't take(1) should ensure that they do.
  map(deptIdArrS => deptIdArrS.map(
    deptId$ => deptId$.pipe(take(1))
  ),
  mergeMap(deptIdArrS => forkJoin(deptIdArrS))
).subscribe(console.log);

Upvotes: 0

martin
martin

Reputation: 96969

mergeMap doesn't flatten the output from its inner Observable. It just reemits when the inner Observable emits and that's it. So it looks like you want to use forkJoin here:

mergeMap(deptIds => forkJoin(
  deptIds.map(deptId => this.store.select(getDepartmentManagers, {
    departmentId: deptId
  }).pipe(take(1)))
)),

Then observers will receive a single array of results for deptIds because forkJoin will wait until all of the inner Observavbles complete.

Upvotes: 2

Related Questions