Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to merge multiple observables into one in Angular 2 project

I am trying to get my head around Observables in RxJs. I have a page where I need to display all users for a specific site. User and SiteUser entities are in separate API endpoints. I have the following endpoints

userService.getSiteUsers(siteId: string): Observable<SiteUser[]>;

where

export class SiteUser {
    site_id: string;
    user_id: string;
}

and

userService.getUser(user_id: string): Observable<User>;

where

export class User {
    id: string;
    name: string;
    email: string;
    ....
}

So I have to do the following:

  1. Call siteUsers API to get all user ids for specific site
  2. For each user id make a getUser API call to get user details

I can easily do this

let users: User[] = []; // this is bound in html view to a table
this.userService.getSiteUsers("my site id")
                .subscribe((siteUsers) => {
                    for (let siteUser of siteUsers) {
                        this.userService.getUser(siteUser.user_id)
                                        .subscribe((user) => {
                                            users.push(user);
                                        });
                    }
                });

But this approach feels dirty or cumbersome. I am sure there is a much cleaner Observable way of doing it. I am very new to Observables, but as far as I understand I should be able to do something like this (not selectMany and mergeAll function are just my guesses, I tried it and it didn't work, I couldn't even find selectMany in rxjs library))

Get site user observable array -> for each element in observable array create user observable -> merge them all into observable array of users -> subscribe, so something like this:

this.userService.getSiteUsers("my site id") 
                .selectMany((siteUser) => this.userService.getUser(user))
                .mergeAll()
                .subscribe((users) => {
                    this.users = users; 
                });

Can someone please help, I can't get it working

EDIT------

Maybe something like this

this.userService.getSiteUsers("my site id")
    .switchMap(
       (siteUsers) => {
         let userQueries: Observable<User>[] = [];
         for (let siteUser of siteUsers) {
            userQueries.push(this.userService.getUser(siteUser.user_id));
         }

         return Observable.forkJoin(userQueries);
       }
    )
    .subscribe((users) => {
        this.users = users;
    });
like image 528
fenix2222 Avatar asked Nov 01 '16 11:11

fenix2222


2 Answers

You should use the .flatMap() / .mergeMap() operator if one http call depends on another http call.

For example in your case something like this would do,

this.userService.getSiteUsers("my site id")
.switchMap(
   (siteUsers) => {
     let userQueries: Observable<User>[] = [];
     for (let siteUser of siteUsers) {
        userQueries.push(this.userService.getUser(siteUser.user_id));
     }

     return Observable.forkJoin(userQueries);
   }
)
.subscribe((users) => {
    this.users = users;
});
like image 164
eko Avatar answered Sep 23 '22 21:09

eko


Try something like this:

this.userService.getSiteUsers("my site id")
  .flatMap((siteUsers) => {
    // map every user into an array of observable requests
    const usersObservables = siteUsers.map(siteUser => this.userService.getUser(siteUser.user_id)).map((res:Response) => res.json())
    return Observable.forkJoin(...usersObservables)
  }).subscribe(users => {
      //you have all your users now;
      console.log(users)
  });

We use the spread operator in here:

return Observable.forkJoin(...usersObservables)

So we transform our array into arguments, like this:

return Observable.forkJoin(observableUser1, observableUser2, observableUser...)
like image 43
Fabio Antunes Avatar answered Sep 20 '22 21:09

Fabio Antunes