简体   繁体   中英

Chaining recursive RxJS observables while operating on object

My backend has two graphql queries: familiyTreeQuery which returns a family tree as a node with children, which can also have children and so on, and personPicturesQuery($name: string) which returns pictures of the given person.

My goal is to call the familyTreeQuery , iterate the tree and if the person is called Alice, then I want to call the personPicturesQuery for the person and add the information to the node in the tree. This should be done for all Alices in the tree.

My problem is, that the call to fetch pictures happens asynchronous and the data is therefore returned before the information is added to the node. I failed to use flatMap as suggested in this question , because the call to fetch the pictures is happening while iterating the tree and I can't call it in the pipe method of the familyTreeQuery .

public getContext(): Observable<TreeNode> {
  return this.familyTreeQuery.watch().valueChanges.pipe(
    map(result => {
      const familyTree = result.data.familyTree;;
      this.addPicturesToAlice(familyTree);
      return familyTree;
      
    })
  )
}

private addPicturesToAlice(node: TreeNode) {
  if (node.name === 'Alice') {
    this.fetchPictures(node.id).subscribe(pictures => {
      node.pictures = pictures;
    })
  }
  if (node.children && node.children.length > 0) {
    for (const childNode of node.children) {
      this.addPicturesToAlice(childNode);
    }
  }
}

private this.fetchPictures(personId): Observable<Picture[]> {
  return this.personPicturesQuery.fetch({id: personId}).pipe(
    map(result => {
      return result.data.personPictures;
    })
  )
}

From what I understand I'm not supposed to call subscribe in the addPicturesToAlice method, but I'm new to Angular and RxJS and didn't find a way to make this work.

You can achieve that by creating an array of observables and passing it along recursively, then subscribing to it in getContext using forkJoin , as demonstrated below:

public getContext(): Observable<TreeNode> {
  return this.familyTreeQuery.watch().valueChanges.pipe(
    switchMap(({ data: { familyTree } }) => forkJoin(this.addPicturesToAlice(familyTree)))
  )
}

private addPicturesToAlice(node: TreeNode, observables: Observable<Picture[]>[] = []): Observable<Picture[]>[] {
  if (node.name === 'Alice') observables.push(
    this.fetchPictures(node.id).pipe(tap(pictures => node.pictures = pictures))
  )

  if (node.children?.length) {
    for (const childNode of node.children) {
      this.addPicturesToAlice(childNode, observables);
    }
  }

  return observables;
}

private fetchPictures(personId: number): Observable<Picture[]> {
  return this.personPicturesQuery
    .fetch({ id: personId })
    .pipe(map(result => result.data.personPictures))
}

Hope it's clear enough.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM