简体   繁体   中英

How to combine multiple rxjs observables with a single subscription?

I have an observable to get users , and I then want to do a API calls to get roles for each user, and then combine all the data into a single array of objects for display.

I have gotten to this point, but I think there must be a way for me to combine the roles calls with the users in stream rather than having to subscribe and then combineLatest:

  this.getAllUsersParams$
    .pipe(
      switchMap(params => this.userFacadeService.sysadminGetAllUsers(params)),
      map(allUsers => {
        const rolesCalls: Observable<UserRoles[]>[] = [];

        allUsers.users.forEach(user => {
          rolesCalls.push(this.userFacadeService.getUserRoles(user.id));
        });

        return { allUsers, rolesCalls };
      })
    )
    .subscribe(data => {
      combineLatest(data.rolesCalls)
        .pipe(
          map(userRoles => {
            const userListVM: UserListVM[] = data.allUsers.users.map((user, i) => {
              return {
                ...user,
                roles: userRoles[i],
              };
            });
            return { userListVM, total: data.allUsers.total };
          })
        )
        .subscribe(data => {
          // consume data
        });
    })

Are you looking for something like this?

this.getAllUsersParams$
  .pipe(
    switchMap(params => this.userFacadeService.sysadminGetAllUsers(params)),
    switchMap(allUsers => combineLatest(
      allUsers.users.map(user => combineLatest([
        of(user),
        this.userFacadeService.getUserRoles(user.id),      
      ])
    )),
    map((result): { total: number, userListVM: UserListVM[] } => ({
      total: result.length,
      userListVM: result.map(user => ({
        ...user[0],
        roles: user[1]
      }))
    }))
  )
  .subscribe(data => {
    ...
  });

It pairs each user with their roles, then you can manipulate the data to be the structure you want.

If you do the mapping right when you retrieve the user's role, you can skip the need for looking up values later. The following maps the array of users to an observable that emits a single UserListVM object. From there the forkJoin executes each observable and returns an array of results. Finally map adds the total property to the emitted object.

this.getAllUsersParams$.pipe(
  switchMap(params => this.userFacadeService.sysadminGetAllUsers(params)),
  switchMap(allUsers => 
    forkJoin(allUsers.users.map(user => 
      this.userFacadeService.getUserRoles(user.id).pipe(
        map(roles => ({ user, roles } as UserListVM) )
      )
    ))
  ),
  map(userListVM => ({ userListVM, total: userListVM.length })) // userListVm: UserListVM[]
)

You might consider using concat and toArray instead of forkJoin as it will execute all observables simultaneously. The following will execute each role request one by one and then when the last executes convert the emissions into a single array.

this.getAllUsersParams$.pipe(
  switchMap(params => this.userFacadeService.sysadminGetAllUsers(params)),
  switchMap(allUsers => 
    concat(...allUsers.users.map(user => 
      this.userFacadeService.getUserRoles(user.id).pipe(
        map(roles => ({ user, roles } as UserListVM))
      )
    ))
  ),
  toArray(),
  map(userListVM => ({ userListVM, total: userListVM.length }))
)

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