Home > Back-end >  RxJS Execute Requests Based on Prior Data Responses (Involves Looping Through Array of Objects)
RxJS Execute Requests Based on Prior Data Responses (Involves Looping Through Array of Objects)

Time:07-18

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.

CodePudding user response:

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)

CodePudding user response:

My first thought was a chain of calls (nested subscribes) until I got to the point where you said it's already doing that.

Considering the replacement you're looking at is, essentially, the same thing (pipe, switchmap, pipe, switchmap...), it should tell you something about the most likely way to go about the task as it stands.

It might not be your preferred method, but perhaps leaving it as several nested subscribes is the answer. Is using a specific rxjs function better? Maybe. Is is more readable/easily understood by others without having to go look at rxjs documentation? Probably not.

Can it be improved? Almost certainly.

Almost any nesting can probably be improved - if required. I imagine you have 3 sets of calls/subscriptions on the go (because that's the levels your data has), but what if there were more? It's a fairly simple tree structure, so the nesting could probably be turned into something more generic and account for any number of levels for future expansion.

Is it worth it? Probably not for your case.

Should you approach it differently? Maybe.

Are you bound to making individual calls for each level? Is there a reason you can't just send the whole object and let the server sort it and give you back the full response?

Less work for the client, less round-trips, simplified code all together.

  • Related