loelu
loelu

Reputation: 85

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.

Upvotes: 1

Views: 308

Answers (1)

Rafi Henig
Rafi Henig

Reputation: 6424

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.

Upvotes: 1

Related Questions