1

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.

8
  • 1
    where is structuredTrees defined? Commented Sep 3, 2020 at 10:27
  • was supposed to be familyTree, edited the question Commented Sep 3, 2020 at 10:31
  • You could consider swapping from Observable to Promise here and using the async-await approach. Commented Sep 3, 2020 at 10:33
  • 1
    As noted in the comment in the code, you can just pretend it does. I simplified the code for the question, in reality it handles other stuff Commented Sep 3, 2020 at 11:13
  • 1
    In order to answer your question it's quite necessary to unerstand how it does, so we can refactor addPicturesToAlice Commented Sep 3, 2020 at 11:57

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Seems like this will work, thank you very much! One more question, how would I do it if the familyTreeQuery returns multiple familiyTrees? Is there another RxJS method I can use before the switchMap?
please take a look at this stackoverflow.com/questions/42482705/…

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.