2

I have an object with an array of families. Each one of these families holds an array of family member objects. Each of the family members has a contract which is not present right now. My task is to fetch the contract from a rest api and to merge it into the member object.

Object {
 content: [{
  familiId: '1',
  familyMemmbers: [
   { id: '1'
     name: 'Tom'
     ...
    },
    { id: '2'
     name: 'Peter'
     ...
    }
   ]
 },
{
  familiId: '2',
  familyMemmbers: [
   { id: '1'
     name: 'Paul'
     ...
    },
    { id: '2'
     name: 'Joe'
     ...
    }
   ]
 }]
}
this.service.getFamilies().switchMap(familiyPage => from(familiyPage.content))
          .switchMap(family => from(family.familyMembers)).mergeMap(member => this.service.getContract(member.id).map(contract => {
          return {...member, contract}

My approach returns the object { id: 1, name: 'Tom', contract: ....} on the first emit and { id: 2, name: 'Peter', contract: ....} on the second emit, { id: 1, name: 'Paul', contract: ....} on the third emit and { id: 2, name: 'Joe', contract: ....} on the fourth emit.

What i would like to have is that there is only one emit which contains the whole data structure. i.e.

Object {
 content: [{
  familiId: '1',
  familyMemmbers: [
   { id: '1',
     name: 'Tom',
     contract: {...},
     ...
    },
    { id: '2',
     name: 'Peter',
     contract: {...},
     ...
    }
   ]
 }, ...]
}
2
  • Do it serverside by having an api that takes an array, don't fire multiple http requests. That is a bad api design that doesn't support your frontend needs. Commented Sep 25, 2019 at 19:24
  • @AdrianBrand that's not necesarily true. RESTful style dictates that you should be front end agnostic. There's also nothing inherently better about single calls sending more data vs many calls sending less data (chunky vs chatty API)... chunky calls stress databases more and chatty APIs stress web servers more, but web servers are easier to scale horizontally, as are databases requesting chatty data... it really depends on the nature of the data and your needs Commented Sep 25, 2019 at 19:55

3 Answers 3

2

Use forkJoin to group the array of observable members into an observable array of members. Then use toArray to group the observable families into an observable array of families. Use mergeMap if you do not care about the order of the families, or concatMap if you want to keep the families in their original order.

this.service.getFamilies().pipe(
  // use concatMap instead of mergeMap if you want to keep the families in their original order
  mergeMap(family => {
    // array of observables
    const members$ = family.familyMembers.map(member => {
      return getContract(member.id).pipe(map(contract => {...member, contract});
    });

    // use forkJoin to return single observable that returns array of results
    return forkJoin(members$).pipe(map(familyMembers => {...family, familyMembers}));
  }),
  // combine all the families back into an array of families
  toArray());
Sign up to request clarification or add additional context in comments.

1 Comment

pipe(map(contract => {...member, contract}) this line was particularly helpful in order to retain the original iterated element and preserve it for a new object. Thanks mate!
0

you're operating on arrays within an array, so you need nested forkJoins to accomplish this along with multiple assignments.

   this.service.getFamilies().pipe(
     switchMap(familiesData => 
       forkJoin(
         familiesData.content.map(family => 
           forkJoin(
             family.members.map(member => 
               this.service.getContract(member.id).pipe(
                 map(contract => Object.assign({}, member, {contract})) // assign contract
               )
             )
           ).pipe(
             map(familyMemmbers => Object.assign({}, family, {familyMemmbers})) // assign members
           )
         )
       ).pipe(
         map(content => Object.assign({}, familiesData, {content})) // assign content
       )
     )
   );

step by step:

  1. get families

  2. switchMap to forkJoin of

  3. content mapped into a forkJoin of

  4. family members mapped into getContract

  5. assign resulting contracts to members and return members with contracts

  6. assign resulting familyMembers to families and return families with familyMembers

  7. assign resulting families to content and return new content

Comments

0

Just add rxjs pipeAble operator toArray() this will combine all the emitted

you can see the below code, where we are streaming the array of objects then streams family members and request HTTP call for each family member then map the response with the family member Object.than again mapping the resultant family Members with the family object and merge them using toArray().

const appDiv = document.getElementById('app');
appDiv.innerHTML = `<h1>if you dont want concurrent request then you can use concat instead of merge</h1>`;
const { fromEvent, merge, of, from } = rxjs;
const { map, filter, concatMap, toArray, tap, delay, concatAll, mergeMap,mergeAll } = rxjs.operators;
// Write TypeScript code!
console.clear();

const data$ = of([{
  familiId: '1',
  familyMemmbers: [
   { id: '1',
     name: 'Tom'
    },
    { id: '2',
     name: 'Peter'
    }
   ]
 },
{
  familiId: '2',
  familyMemmbers: [
   { id: '1',
     name: 'Paul'
    },
    { id: '2',
     name: 'Joe'
    }
   ]
 }])
const q = (x) => {
  // Fake http call 
  const oneSecondSource$ = of(x['id']).pipe(delay(1 * x.id))
  return oneSecondSource$.pipe(map(abs => {
    
    return { ...x, contarct: x['id'] + x['name'] }
  }));
}

const p = (obj) => {
  // mapping each
  return from(obj.familyMemmbers).pipe(mergeMap(q), toArray(), map(a => {
    return { ...obj, familyMemmbers: a }
  }))
}
const example$ = data$.pipe(mergeMap(a => from(a).pipe(map(p))),
  mergeAll(),
  toArray()
);
example$.subscribe(console.log)
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>

<div id="app"></div>

For your Question click here for Stackblitz solution

2 Comments

That is true but the result is an array of members with a contract. But i is unclear which member belongs to which family. That's why i want to get back the whole data structure.
you can check the solution with updated code and stackblitz reference

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.