RX JS: возвращение вложенных наблюдаемых - PullRequest
0 голосов
/ 27 февраля 2020

Я пытаюсь вернуть объект UserDetail, который состоит из пользователя и результатов. Пользователь извлекается через accessToken of Account (все извлекается с помощью отдельных асин c вызовов). В данный момент я застрял в выяснении, как я могу вернуть этот объект UserDetail при переходе к компоненту detail (я знаю, что невозможно буквально вернуть объект, так как вызов asyn c, но мне нужно использовать это объект в моем компоненте).

Приведенный ниже код дает мне следующую ошибку:

Тип «Наблюдаемый» не может быть назначен типу «Наблюдаемый»

Я попытался использовать каналы и карты, так как я прочитал, как я должен «возвращать» асин c вызовы компонента, вызывающего функцию. Компонент должен обрабатывать подписку, но я не могу пройти так далеко, не создав ошибок.


@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);
                })
              );
            }
          })
        );
      })
    );

Ответы [ 2 ]

1 голос
/ 28 февраля 2020

Прежде всего вам следует избегать вложенных трубопроводов, когда это возможно, чтобы их можно было обслуживать. Вместо этого объедините Observables.

Но для вашей проблемы: просто удалите "of (ud)", а затем просто верните ud на вашей последней карте.

Типовое удобство обслуживания:

Я вижу, что у вас есть один вызов, который не нужно разрешать по порядку.


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
                                  }));

1 голос
/ 27 февраля 2020

Попробуйте использовать switchMap вместо map. map просто преобразует одно значение в другое, тогда как switchMap позволяет переключаться на другое наблюдаемое.

Не зная слишком много о том, что происходит, я думаю, вы хотите что-то вроде этого:

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;
  })
);

Обратите внимание, что это больше не отступ, как у вас, а последовательность операторов канала. Всякий раз, когда вы хотите переключиться на новую наблюдаемую, используйте switchMap или concatMap. Мы используем map только для сопоставления результата последней наблюдаемой со значением, которое мы хотим вернуть из функции.

Всякий раз, когда необходимо сохранить состояние из середины канала, я использую нажмите, чтобы назначить ее переменной.

Кроме того, вы делаете (возможно) избыточные вызовы this.rs.getResults(this.auth.token). Вызов не изменяется в зависимости от того, что является параметром id, так что вы можете просто извлечь один раз и прочитать из кэша при последующих вызовах.

Edit: concatMap также вариант. Они немного разные. И того, и другого достаточно для ответа на этот вопрос - я не собираюсь делать предположения относительно вашего варианта использования.

...