如何在使用订阅的嵌套for循环中重构HttpRequest?

时间:2020-07-10 14:37:39

标签: angular typescript rxjs rxjs-observables

我们有一个与前端无关的REST-API,这意味着它始终将IRI发送到其嵌套资源。因此,要检索某些数据,您始终必须进行多个http调用(首先获取父资源,然后获取其子资源,等等)。 因此,每个国家/地区都有一个链接条目列表。每个条目都链接到一个产品,该产品的类别资源具有IRI。

export interface Country {
  countryId: number;
  name: string;
  code: string;
  projects: string[]; //contains an array of IRIs for the project resources
  entries: string[];
}

export interface Entry {
  entryId: number,
  country: string,
  information: string,
  strategy: string,
  action: string,
  user: string,
  product: string,
  salesStatus1: string,
  salesStatus2: string,
  salesStatus3: string,
  salesStatus4: string,
  salesStatus5: string,

}

export interface Product {
  productId: number,
  productName: string,
  sharepointId: string,
  category: string,
  plmId: string,
  productStatus1: string,
  productStatus2: string,
  productStatus3: string,
  productStatus4: string,
  productStatus5: string,
  productComponents: string[]
}

export interface Category {
  categoryId: number,
  name: string,
  children: string[]
}
export class Node {
  children: Node[] = [];
  name: string;
  isProduct: boolean;
}

因此,为了使用显示导航树所需的所有数据,我编写了以下代码:

ngOnInit() {
    let nodes: Node[] = new Array<Node>();
    this.countriesService.getAll()
      .subscribe(
        (countries) => {
          for (let country of countries) {
            let countryNode = new Node();
            countryNode.name = country.name;
            countryNode.isProduct = false;
            for (let entryUrl of country.entries) {
              this.entriesService.getByUrl(entryUrl).subscribe(
                (entry) => {
                  this.productsService.getByUrl(entry.product).subscribe(
                    (product) => {
                      this.categoryService.getByUrl(product.category).subscribe(
                        (category) => {
                          let categoryNode = new Node();
                          categoryNode.name = category.name;
                          categoryNode.isProduct = true;
                          countryNode.children.push(categoryNode);
                          for (let childrenUrl of category.children) {
                            this.categoryService.getByUrl(childrenUrl).subscribe(
                              (childCategory) => {
                                let categoryChildNode = new Node();
                                categoryChildNode.name = childCategory.name;
                                categoryChildNode.isProduct = false;
                                categoryNode.children.push(categoryChildNode);
                              }
                            )
                          }
                        }
                      )
                    }
                  )
                }
              )
            }
            nodes.push(countryNode);
          }
          this.dataChange.next(nodes);
        }
      );
  }

但是,由于我是Angular和rxjs的新手,因此我遇到了“等待”问题,直到所有调用完成并且所有数据(由于其异步)在那里(因此导航树始终会丢失元素)。像这样进行链接也很丑陋和不好的做法。所以我想重构 rxjs方法的代码,但是我完全迷失了如何开始使用它,因为检索数据后,我必须再次对其进行迭代以获取嵌套的资源IRI,还必须为树导航创建Node对象。 / p>

您能否在重构该代码方面给我一些帮助?

1 个答案:

答案 0 :(得分:0)

不确定这是否有帮助,我将请求与rxjs运算符组合在一起。

this.countriesService.getAll()
  .pipe(
    map((cs) => forkJoin(cs.map(c => {
      const countryNode = new Node();
      countryNode.name = c.name;
      countryNode.isProduct = false;

      return forkJoin(c.entries.map(entryUrl => this.entriesService.getByUrl(entryUrl)))
        .pipe(
          switchMap(entries => forkJoin(entries.map(entry => this.productsService.getByUrl(entry.product)))),
          switchMap(products => forkJoin(products.map(product => this.categoryService.getByUrl(product.category)))),
          switchMap(categories => forkJoin(categories.map(category => {
            const categoryNode = new Node();
            categoryNode.name = category.name;
            categoryNode.isProduct = true;

            return forkJoin(category.children.map(childrenUrl => this.categoryService.getByUrl(childrenUrl)))
              .pipe(
                map(cc => {
                  categoryNode.children = cc.map(childCategory => {
                    const categoryChildNode = new Node();
                    categoryChildNode.name = childCategory.name;
                    categoryChildNode.isProduct = false;
                    return categoryChildNode;
                  });
                  return categoryNode;
                })
              );
          }))),
          map(categoryNodes => {
            countryNode.children = categoryNodes;
            return countryNode;
          })
        );
    })))
  ).subscribe(countryNodes => {
    this.dataChange.next(countryNodes)
  });