Search code examples
angularrxjsobservablerxjs-pipeable-operators

RxJS Execute Requests Based on Prior Data Responses (Involves Looping Through Array of Objects)


I have an object of the following structure:

{
 parent1: [
     {childId: 1},
     {childId: 2}
    ],
 parent2: [
     {childId: 3},
     {childId: 4}
    ],
 parent3: [
     {childId: 5},
     {childId: 6}
 ]
}

The following services:

addFamily(data: any): Observable<Family> {
     const body = JSON.stringify(data);
     return this.httpClient
          .post<Family>(this.apiUrl + '/family', body, this.httpOptions)
}
addParent(data: any): Observable<Parent> {
     const body = JSON.stringify(data);
     return this.httpClient
          .post<Parent>(this.apiUrl + '/parent', body, this.httpOptions)
}
addChild(data: any): Observable<Child> {
     const body = JSON.stringify(data);
     return this.httpClient
          .post<Child>(this.apiUrl + '/child', body, this.httpOptions)
}

There are corresponding "family", "parent", and "child" tables in the database and I want to POST to each of these tables accordingly using RxJS higher order mapping operators to build a data response from prior calls. The idea is:

  1. call addFamily() and return a newfamilyId
  2. call addParent() passing in familyId and returning a new parentId
  3. call addChild() passing in parentId for each child created

After performing the operations on the example object, there will be:

  • 6 children added (FK child_parent)
  • 3 parents added (FK parent_family)
  • 1 family added

Currently the codebase is using multiple nested subscribes to perform the above tasks, which is why I looked into RxJS and stumbled upon the below code from a similar question.

private getData(): Observable<VmData> {

     return this.service.getSomeData().pipe(
                 switchMap(session => this.service.getUserData(session.userId).pipe(
                      switchMap(user => this.service.getMetaData(user.id).pipe(
                           // by nesting, map has access to prior responses
                           map(userMeta => this.buildVmData(session, user, userMeta))             
                      ))
                 )),
                 tap(() => this.isLoading = false)
     );
}

The above code would work if it was one family -> one parent -> one child, but how can I modify it to add 1 family, loop through each parent, and add each children under that parent? Any help is greatly appreciated. Thank you.


Solution

  • What if you break down the problem into smaller composable blocks? I can see 3 functions:

    1. createFamily will call addFamily(), then use createParent for each parent of that family.
    2. createParent will call addParent(), then use createChild for each child of that parent.
    3. createChild will call addChild()

    Something like this

    function createFamily(family) {
      return defer(() => {
        const familyId = generateFamilyId();
    
        return addFamily({ familyId });
      }).pipe(
        switchMap((result) => {
          const { familyId } = result;
          const parentCreations = Object.entries(family).map(
            ([parentId, children]) => createParent({ familyId, parentId, children })
          );
    
          return merge(...parentCreations);
        })
      );
    }
    
    function createParent(parent) {
      return addParent(parent).pipe(
        switchMap(() => {
          const { familyId, parentId, children } = parent;
    
          const childrenCreations = children.map(({ childId }) =>
            createChild({
              familyId,
              parentId,
              childId,
            })
          );
          return merge(...childrenCreations);
        })
      );
    }
    
    function createChild(child) {
      return addChild(child);
    }
    

    createFamily(family) in this case will return an Observable<Child> emitting all children that was created. You can easily transform this to anything you'd need by changing the .pipe( of each one of the functions (this is why I also left createChild as a separate function of addChild, the idea is you can do transformations there if you need)