Angular2: is this the best way to make multiple sync calls to Observables?

I am learning Observables in Angular2 / 4 before I used Promises for my service call.

I am wondering what is the best way to make multiple sync calls. Let me explain with an example: my application component has a getUserInfo () method that should make 3 calls to 3 different services that depend on each other.

getUserId():Number // return the logged in user userId from the session
getUserPermissions(userId):Array<String> // return a list of permission from the userId
getUserInfo(userId):String // return some additional info for the user

      

Now, suppose I have a User object like the following:

export class User {
id: number;
name: string;
info: string;
permissions: array<string>;
}

      

I need to create a new instance of the User class with the results of the 3rd service call, so I need to run:

  • getUserId ();
  • getUserPermissions ();
  • GetUserInfo ();

What's the best and most polite way to accomplish this through an Observable?

With Promises, I would have something like this:

this._service.getUserId().then(r1 => {
  let userId: number = r1;
  this._service.getUserPermissions(userId).then(r2 => {
    let userPermissions: Array<String> = r2;
    this._service.getUserInfo(userId).then(r3 => {
      let userInfo: string = r3;
      let user: User = new User(userId, userInfo, userPermissions);
    });
  })
});

      

+3


source to share


1 answer


I cannot guarantee that this is the best or most polite way, because RxJs is such a powerful library where you can achieve the same result in many different ways, but I'll give it a shot. I will chip in two versions.

Assuming your service looks something like this:

userservice.ts

@Injectable()
export class UserService {

  constructor(private http: Http) { }

  getUserId(): Observable<{ id: number, name: string }> {
    return Observable.of({ id: 3, name: 'Bob' }).delay(300);
    /* 
     * replace with http:
     * return this.http.get('http://my.api.com/api/user/id').map(res => res.json());
     */
  }

  getUserPermission(userId: number): Observable<{ permissions: string[] }> {
    return Observable.of({ permissions: ['user', 'admin'] }).delay(300);
    /* return this.http.get(`http://my.api.com/api/user/${userId}/permissions`).map(res => res.json()); */
  }

  getUserInfo(userId: number): Observable<{ info: string }> {
    return Observable.of({ info: 'is a nice person'}).delay(300);
    /* return this.http.get(`http://my.api.com/api/user/${userId}/info`).map(res => res.json()); */
  }
}

      

Note that the methods return Observables from JSON objects!
Since Angular http already returns Observables, it is probably the simplest and cleanest to keep the Observable chaining up to the end.
You can of course use the map

-operator (fe .map(result => result.info)

) inside a service method to make the return type Observable<string>

instead Observable<{ info: string }>

.


switchMap

This approach is suitable for queries that must be executed in a specific order.

this.userService.getUserId()
  .switchMap(userResult =>
    this.userService.getUserPermission(userResult.id)
    .switchMap(permissionsResult =>
      this.userService.getUserInfo(userResult.id)
        .map(infoResult => ({
          id: userResult.id,
          name: userResult.name,
          permissions: permissionsResult.permissions,
          info: infoResult.info
        }))
    )
  )
  .subscribe(v => console.log('switchmap:', v));

      



If you open your browser's network tab, you will see that the requests are being executed sequentially, which means that each request has to finish before the next run. So it getUserId()

must end before the start getUserPermission()

, which in turn must end before it getUserInfo()

can start ... etc.

You can use instead mergeMap

. The only difference is that switchMap can cancel the current http request when a new value is emitted by the source being watched. Take a look here for a good comparison.

forkJoin

This approach allows queries to be executed in parallel.

this.userService.getUserId()
  .switchMap(userResult => Observable.forkJoin(
    [
      Observable.of(userResult),
      this.userService.getUserPermission(userResult.id),
      this.userService.getUserInfo(userResult.id)
    ]
  ))
  .map(results => ({
    id: results[0].id,
    name: results[0].name,
    permissions: results[1].permissions,
    info: results[2].info
  }))
  .subscribe(v => console.log('forkJoin:', v));

      

Since it forkJoin

runs all Observable sequences that were given in parallel, this is the best option if the requests (or at least some of them) are independent of each other.
In this example, the first run getUserId()

-request, and once it is completed, the two getUserPermission()

and getUserInfo()

will operate in parallel.


Both methods return an object with the following structure:

{
    "id": 3,
    "name": "Bob"
    "permissions": [
        "user",
        "admin"
    ],
    "info": "is a nice person"
}

      

+7


source







All Articles