-1

we have an REST-API that is frontend agnostic, which means that it always sends the IRI to its nested resources. So to retrieve some data you always have to make multiple http calls (first get the parent resource, then its child resources etc.) So each Country has a list of linked Entries. Each entry is linked to a product, which has a IRI to its category resource.

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

So to consume all data that i needed to display an navigation tree i wrote the following code :

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

However as i am kinda new to Angular and rxjs i am having problems to "wait" until all calls are finished and all data (as its asynchronous) is there (and therefore the navigation tree always misses elements). Also it is kinda ugly and bad practice to chain like this. So i wanted to refactor the code to rxjs-methods, however i am completly lost how to even start with it, because after retrieving data i have to iterate over it again to get the nested resource IRIs and also have to create Node objects for the tree-navigation.

Can you pls give me some help, on how to refactor that code?

Toxuas
  • 1
  • 3
  • I believe it is already answered in https://stackoverflow.com/questions/45161800/chain-and-merge-3-rxjs-observables-with-result-dependences-without-nesting-in-ty and https://stackoverflow.com/questions/62677272/chain-observable/62678978#62678978 – buchipper Jul 10 '20 at 14:49
  • i would recommend to use promise with async/await syntax instead of subscription in your case – critrange Jul 10 '20 at 15:13
  • 1
    Why..? Everything in Angular is Observables based. Async/await is an anti-pattern in Angular and should really be avoided. Learn RxJs, it is worth it. – MikeOne Jul 10 '20 at 16:09

1 Answers1

0

not sure if this can be helpful, i combinated the requests with rxjs operators.

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)
  });
critrange
  • 5,652
  • 2
  • 16
  • 47