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.

loelu
  • 85
  • 10
  • 1
    where is `structuredTrees` defined? – Rafi Henig Sep 03 '20 at 10:27
  • was supposed to be `familyTree`, edited the question – loelu Sep 03 '20 at 10:31
  • You could consider swapping from Observable to Promise here and using the async-await approach. – MoxxiManagarm Sep 03 '20 at 10:33
  • The problem is that the `getContext()` method would need to be async and return a `Promise` but the methods calling `getContext()` expect an `Observable` and I don't want to change the whole code base. But if there's no other way I may have to – loelu Sep 03 '20 at 10:39
  • Since `fetchPictrues()` takes no params, how do you expect it to fetch different pictures for each node? – Rafi Henig Sep 03 '20 at 10:57
  • 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 – loelu Sep 03 '20 at 11:13
  • 1
    In order to answer your question it's quite necessary to unerstand how it does, so we can refactor `addPicturesToAlice` – Rafi Henig Sep 03 '20 at 11:57
  • I pass information saved in the node to `fetchPictures()`. I edited the code to be more clear – loelu Sep 03 '20 at 12:05

1 Answers1

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.

Rafi Henig
  • 5,950
  • 2
  • 16
  • 36
  • 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`? – loelu Sep 03 '20 at 14:42
  • 1
    please take a look at this https://stackoverflow.com/questions/42482705/best-way-to-flatten-an-array-inside-an-rxjs-observable/42482824#42482824 – Rafi Henig Sep 03 '20 at 14:51