RXJS: returning nested observables

旧城冷巷雨未停 提交于 2021-01-29 16:50:00

问题


I'm trying to return a UserDetail object which consists of a User and Results, User is retrieved via the accessToken of Account (all retrieved via individual async calls). At the moment I'm stuck at figuring out how I can return this UserDetail object when I navigate to the detailcomponent (I know it is not possible to literally return the object since the call is async, but I need to use this object in my component).

The code below gives me the following error:

Type 'Observable' is not assignable to type 'Observable'

I tried using pipes and maps since I've read this is how I should 'return' async calls to the component calling the function. The component is then supposed to handle the subscription, but I can't even get that far without creating errors.


@Injectable({
  providedIn: 'root'
})
export class UserResolver implements Resolve<UserDetails> {
  constructor(
    private as: AccountService,
    private rs: ResultService,
    private auth: AuthService
  ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<UserDetails> {
    return this.as.getAccount(route.params['id']).pipe(
      map((acc: Account) => {
        this.as.getUserDetails(acc.access_token).pipe(
          map((user: User) => {
            if (user != null) {
              this.rs.getResults(this.auth.token).pipe(
                map((res: Result[]) => {
                  const ud: UserDetails = {
                    user,
                    results: res.filter(x => x.userId === acc.uid)
                  };
                  return of(ud);
                })
              );
            }
          })
        );
      })
    );

回答1:


Try using switchMap instead of map. map simply transforms one value into another, whereas switchMap allows you to switch to another observable.

Without knowing too much about what's going on, I think you want something like this:

let user: User;
let account: Account;
return this.as.getAccount(route.params['id']).pipe(
  tap((acc: Account) => account = account),
  switchMap((acc: Account) => this.as.getUserDetails(acc.access_token)),
  // this filter will stop the next steps from running if the user is null
  // it will also mean a value isn't emitted
  // if you want to emit a null, you will need to modify the pipe
  filter((u: User) => u !== null),
  tap((u: User) => user = u),
  switchMap((u: User) => this.rs.getResults(this.auth.token))
  map((res: Result[]) => {
    const ud: UserDetails = {
      user,
      results: res.filter(x => x.userId === account.uid)
    };
    return ud;
  })
);

Notice how it's no longer indented like yours, but a sequence of pipe operators. Whenever you want to switch to a new observable, use switchMap or concatMap. We are only using map to map the result of the last observable to the value that we want to return from the function.

Whenever state needs to be saved out of the middle of a pipe, I am using tap to assign it to a variable.

Also, you are making (possibly) redundant calls to this.rs.getResults(this.auth.token). The call doesn't change based on the what the id param is, so you could just fetch once, and read from a cache on subsequent calls.

Edit: concatMap is also an option. They are slightly different. Both are sufficient for the purposes of this answer - I'm not going to make assumptions about your use case.




回答2:


first of all you should avoid nested piping when possible so it is maintainable. Instead combine Observables.

But for your problem: just remove "of(ud)" and then just return ud in your last map.

Tipp for maintainability:

I see you have one call wich don't need do be resolved in order.


const userDetails$: Observable<Userdetails> = this.rs.getResults(this.auth.token)
                                         .pipe(map((res: Result[]) => {
                                             const ud: UserDetails = {
                                             user,
                                             results: res.filter(x => x.userId === acc.uid)
                                             };
                                              return ud;
                                             });

const userCheck$: Observable<User> = this.as.getAccount(route.params['id'])
                                     .pipe(map((acc: Account) => 
                                     this.as.getUserDetails(acc.access_token));

// Only when both values are available those observables will be ziped. 
// Only one subscription is needed on hte returned Obersvable 

return zip(userDetails$,userCheck$).pipe(map(([userDetails,user])) => {
                                   if (user != null) {
                                      return userDetails
                                  }else{
                                      // should probably emit a error
                                  }));



来源:https://stackoverflow.com/questions/60436143/rxjs-returning-nested-observables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!