9

My Angular 2 application has 2 methods (GetCategories() and GetCartItems()) in a service , and both of these methods return Observables.

In order to invoke these two methods one after another from my component, I have written below code:

 ngOnInit() 
{
   this.appService.GetCategories().subscribe( (data) => {
       this.appService.categories = data;


       this.appService.GetCartItems().subscribe( {
                                                    next: (data) => { this.appService.cart = data},
                                                    error: (err) => { this.toaster.error('cart==>' + err)}

                                                })

   });       
}

Basically, calling GetCartItems() from within subscribe() of GetCategories(), and I feel this is NOT the right approach. This is kind of callback hell.

Any idea on how to implement this in a better way (like chaining then() in Promises)?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
refactor
  • 13,954
  • 24
  • 68
  • 103
  • 1
    http://stackoverflow.com/questions/36712659/angular-2-two-backend-service-calls-on-success-of-first-service/36712707#36712707 – eko Dec 28 '16 at 12:17

2 Answers2

15

Looks like GetCartItems doens't depend on GetCategories. Then you can use zip:

Observable
    .zip(
        this.appService.GetCategories()
        this.appService.GetCartItems()
    )
    .catch(err => this.toaster.error(err))
    .subscribe(([categories, cartItems]) => {
        this.appService.categories = categories;
        this.appService.cart = cartItems;
    });
Sergey Sokolov
  • 2,709
  • 20
  • 31
  • 3
    I think `Observable.combineLatest` is the right choice, not `Observable.zip` (if I understand the problem). Things may have changed in the year since you wrote this. When I use `Observable.zip` without a third argument as in the above case, one of the inputs is undefined, depending on when the subscription happens to fire. – Brandon Arnold Oct 31 '17 at 16:27
7

This is most typically done with concat(), concatMap() or eventually concatAll() depending on your usecase and whetrher you need to call both services in order or not.

function GetCategories() {
    return Observable.timer(1000).do(() => console.log('GetCategories()'));
}

function GetCartItems() {
    return Observable.timer(1000).do(() => console.log('GetCartItems()'));
}

console.log('start...');

GetCategories()
  .concatMap(() => GetCartItems())
  .subscribe(() => console.log('done'));

This prints to console:

start...
GetCategories()
GetCartItems()
done

Each item is delayed to show these are called in order one after another.

If you don't need to keep the same order you can use merge() or mergeMap().

See live demo: https://jsbin.com/wawajob/1/edit

Note that using zip() might have undesired behavior. See https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/zip.md

martin
  • 93,354
  • 25
  • 191
  • 226