以可观察的方式返回嵌套的forkjoins

时间:2020-03-05 13:18:37

标签: angular rxjs observable

我试图在我的解析器中返回一堆嵌套的forkjoins和normal订阅。为此,我尝试使用地图,但是我想我还没有完全掌握map / switchMaps / mergeMaps的概念。 我知道代码还没有返回UserResult,这是因为我还不知道如何将QuestionAnswers添加到UserResult中,但这与我当前的问题没有太大的区别。

我的目标是重写它,以便它返回可观察的结果。

(?<product>.*?)

我试图像这样重写它,但是它根本不起作用。我遇到了一些未定义的错误,但它们都指向管道的起点,没有具体说明。

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<UserResult> {
    const questionAnswers = Array<QuestionAnswer>();

    this.rs.getResult(this.auth.token, route.params['id']).subscribe(res => {
      forkJoin(
        this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
        this.accs.getAccount(res.userId)
      ).subscribe(results => {
        forkJoin(
          this.accs.getUserDetails(results[1].access_token),
          this.as.getAnswers(this.auth.token)
        ).subscribe(results2 => {
          results[0].forEach(cat => {
            this.cs
              .getQuestionsCategory(this.auth.token, cat.id)
              .subscribe(questions => {
                results2[1]
                  .filter(ans => ans.userId === results[1].uid)
                  .forEach(a => {
                    const question = questions.find(q => q.id === a.questionId);
                    if (!isNullOrUndefined(question)) {
                      const category = results[0].find(
                        c => c.id === a.categoryId
                      );
                      const qa = new QuestionAnswer(question, a);
                      qa.category = category.name;
                      questionAnswers.push(qa);
                    }
                  });
              });
          });
        });
      });
    });
}

编辑

引起我注意的是,点击result2后res [0]引起

无法读取未定义的属性“ 0”

我认为这与我对水龙头的使用不当有关,因为它在我尝试更改的订阅中运行良好。

EDIT2

我将代码拆分为较小的功能,如Kurt建议,但是我不确定如何将其与我用于类别的forEach结合使用。我也不知道应该在哪里创建最终的对象,该对象将以可观察的方式返回

    const questionAnswers = Array<QuestionAnswer>();
    let res;
    let res2;

    return this.rs.getResult(this.auth.token, route.params['id']).pipe(
      map((res: Result) =>
        forkJoin(
          this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
          this.accs.getAccount(res.userId)
        )
      ),
      tap(results => (res = results)),
      map(results =>
        forkJoin(
          this.accs.getUserDetails(results[1].access_token),
          this.as.getAnswers(this.auth.token)
        )
      ),
      tap(results2 => (res2 = results2)),
      map(
        res[0]
          .forEach(cat => {
            this.cs.getQuestionsCategory(this.auth.token, cat.id);
          })
          .map(questions =>
            res2[1]
              .filter(ans => ans.userId === res[1].uid)
              .forEach(a => {
                const question = questions.find(q => q.id === a.questionId);
                if (!isNullOrUndefined(question)) {
                  const category = res[0].find(c => c.id === a.categoryId);
                  const qa = new QuestionAnswer(question, a);
                  qa.category = category.name;
                  questionAnswers.push(qa);
                }
              })
          )
      )
    );

1 个答案:

答案 0 :(得分:2)

所以...那是您那里的RxJS很大一部分。

首先,您不需要在RxJS运算符内进行订阅,而是将可观察对象链接在一起。

一些定义

switchMapconcatMap用于将一个可观察的结果传递给另一个。

map用于将值从一种结构转换为另一种结构(类似于同名数组函数的概念)。

forkJoin组合了多个可观察对象,并在它们全部完成后返回一个结果。

您的代码

在您甚至开始尝试整理代码之前,我建议您考虑考虑将每个步骤拆分为自己的功能。希望这将帮助您查看数据流并考虑依赖项在哪里。

我可以将您的原始示例转换为RxJS,但是在考虑每个步骤实际上试图实现的目标时却迷失了一部分。

我确定的是,您最终会得到一个类似这样的模式(出于这个演示的目的,我正在订阅-您将返回可观察的结果):

result: string;

ngOnInit() {
  this.initialValue().pipe(
    switchMap(result => this.forkJoinOne(result)),
    switchMap(result => this.forkJoinTwo(result)),
    switchMap(result => this.forkJoinThree(result)),
    map(result => this.mapFour(result))
  ).subscribe(result => {
    this.result = result;
  });
}

private initialValue(): Observable<string> {
  return of('zero');
}

private forkJoinOne(result: string): Observable<string[]> {
  return forkJoin([
    of(`${result} one`),
    of('four')
  ]);
}

private forkJoinTwo(results: string[]): Observable<string[]> {
  return forkJoin([
    of(`${results[0]} two`),
    of(`${results[1]} five`)
  ]);
}

private forkJoinThree(results: string[]): Observable<string[]> {
  return forkJoin([
    of(`${results[0]} three`),
    of(`${results[1]} six`)
  ]);
}

private mapFour(results: string[]): string {
  return results.join(' ');
}

每个可观察的步骤都已移入其自己的功能-这可以帮助您考虑需要输入什么数据和要输出什么数据-您正在有效地在每个步骤之间创建合同。

switchMap只是一个可观察的对象,而建立了另一个。最后的map将前面可观察到的输出转换为另一个值。

我在这里使用了字符串,希望可以简化跟踪数据流的过程。我建议从尝试理解我的简单示例开始,然后使用这些原理重新构建您的功能。

演示:https://stackblitz.com/edit/angular-eedbqg

我的版本在以下方面与您的版本大致保持一致:

初始值

this.rs.getResult(this.auth.token, route.params['id'])

forkJoinOne

所有fork联接都应传入数组或对象。我更喜欢传入对象的相对较新的方式,它指明了发射值的结构。 (forkJoin({ a: myObs })返回{ a: value })。

forkJoin(
  this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
  this.accs.getAccount(res.userId)
)

forkJoinTwo

forkJoin(
  this.accs.getUserDetails(results[1].access_token),
  this.as.getAnswers(this.auth.token)
)

forkJoinThree

您将需要将此循环转换为可观察对象数组,并将其传递给forkJoin

results[0].forEach(cat => {
  this.cs.getQuestionsCategory(this.auth.token, cat.id)

mapFour

您将需要整理地图。此处使用forEachfilter(数组函数)代替map

map(questions =>
  res2[1]
    .filter(ans => ans.userId === res[1].uid)
    .forEach(a => {
      const question = questions.find(q => q.id === a.questionId);
      if (!isNullOrUndefined(question)) {
        const category = res[0].find(c => c.id === a.categoryId);
        const qa = new QuestionAnswer(question, a);
        qa.category = category.name;
        questionAnswers.push(qa);
      }
    })