14

I have 3 dependent Rest API resources (lets say observables) like this:

1st observable produces one item as array of users, like this:

getUsers(): Observable<User[]>
    [
      {
        "id": 1,
        "name": "Peter",
        "surname": "Smith"
      },
      {
        "id": 2,
        "name": "John",
        "surname": "Wayne"
      },
      ...
    ]

2nd observable can be used to fetch addresses assigned to user, so the input parameter is User ID, and returns a one item as array of addresses:

getUserAddresses(user_id: string): Observable<Address[]>
    [
      {
        "id": 1,
        "city": "London",
        "street": "Wicombe 34"
      },
      {
        "id": 2,
        "city": "Paris",
        "street": "La fever 20"
      },
      ...
    ]

3rd observable can be used to fetch companies assigned to user, so the input parameter is User ID, and returns a one item as array of companies:

getUserCompanies(user_id: string): Observable<Company[]>
    [
      {
        "id": 1,
        "name": "Fintch CO",
        "state": "France"
      },
      {
        "id": 2,
        "name": "C&C inc.",
        "state": "USA"
      },
      ...
    ]

I want to chain these 3 observables into one which will produce the result again as one item, that will contain array of users with their additional addresses arrays and companies array, like following:

    [
      {
        "id": 1,
        "name": "Peter",
        "surname": "Smith",
        "addreesses": 
               [
                 {
                   "id": "1",
                   "city": "London",
                   "street": "Wicombe 34"
                 },
                 {
                   "id": "2",
                   "city": "",
                   "street": "La fever 20"
                 }
               ],
        "companies": 
               [
                 {
                   "id": "1",
                   "name": "Fintch CO",
                   "state": "France"
                 },
                 {
                   "id": "2",
                   "name": "C&C inc.",
                   "state": "USA"
                 }
               ]
        }
      },
      {
        "id": 2,
        "name": "John",
        "surname": "Wayne",
        "addresses": [...],
        "companies": [...],
      },
      ...
    ]

How should look like the operators composite to achieve that in Angular 6 with RxJs 6? Thanks a lot for advice.

Peter
  • 223
  • 1
  • 3
  • 7

3 Answers3

19

Something like this could work

getUsers()
.pipe(
  switchMap(users => from(users)),
  mergeMap(user => forkJoin(getAddresses(user.id), getCompanies(user.id))
                   .pipe(map(data => ({user, addresses: data[0], companies: data[1] })))
  tap(data => data.user.addresses = data.addresses),
  tap(data => data.user.companies = data.companies),
  map(data => data.user),
  toArray()
  )
)

Applied to a different use case a similar chain is explained in more details here.

minni
  • 1,144
  • 11
  • 9
Picci
  • 16,775
  • 13
  • 70
  • 113
  • and how will look like the operators chain if the source 1st observable produce the users's array as an inner property of some other simple one object like this: { "group_name": "employees", "group_id": "1", "users": [...] } – Peter Jul 15 '18 at 20:40
  • If ai understand correctly your point you should just have ‘switchMap(data => from(data.users))’ as the first operator in the chain – Picci Jul 15 '18 at 20:56
  • yes this will work if I am expecting the same result (only users array), but what in case when I want at the end result all information included from 1st observable (group_name, group_id) ??? – Peter Jul 15 '18 at 21:10
  • what if I don't need the companies array. only addresses I need? @Picci – ilovejavaAJ Jul 17 '19 at 06:27
3

To chain observables, you can use the flatMap function. This function is like promises then, meaning the function is only executed once the observable has resolved to a value.

There is also an observable function called forkJoin which allows you to wait for multiple async requests to all have resolved to a value.

Example:

getUsers().flatMap((users) => {
    // the users variable is the list of users
    var multipleObservables = [];
    users.forEach((u) => {
      multipleObservables.push(
         getUserAddresses(u.id).map((usersAddresses) => {
            u.addresses = usersAddresses;
            return u;
         });
      );
    });
    return forkJoin(multipleObservables);
}).flatMap((usersWithAddresses) => {
    // the usersWithAddresses variable is an array of users which also have their addresses
    var multipleObservables = [];
    usersWithAddresses.forEach((u) => {
       multipleObservables.push(
          getUserCompanies(u.id).map((usersCompanies) => {
             u.companies = usersCompanies;
             return u;
          });
       );
    });
    return forkJoin(multipleObservables);
});

You can write something like this to chain you request and grow your user objects.

brad mcallister
  • 419
  • 2
  • 13
  • this approach is not working for me, the u.id is undefined – Peter Jul 06 '18 at 17:05
  • Is the object returned by getUsers() an array of users? What exactly is contained inside of the users variable? – brad mcallister Jul 06 '18 at 23:01
  • the exact data structure returned by getUsers() are showed in my example – Peter Jul 10 '18 at 12:56
  • I am not sure why this is happening the users object should be an array of the user objects. I guess as a test you can do `console.log(users[0].id)` before the line `var multipleObservables = [];` and see if you get an id. – brad mcallister Jul 10 '18 at 20:06
0

Here are the operators for combination, these emit the result in an array (reference):

I would suggest giving them all a read, to work out what you would prefer and find best for your scenario.

Hope this helps!

Jmsdb
  • 515
  • 3
  • 9