Angular 6 / Rxjs - how to basics: observables success, error, finally

Simon Peyou picture Simon Peyou · Jun 28, 2018 · Viewed 44.2k times · Source

I'm building an architecture on latest Angular 6 and coming from AngularJS there's something I can't make peace about: the basic processing of an HTTP request.

So, for the sake of the question, let's say I want an observable. Because it seems to be the future of Angular.

I went from something very elegant, in AngularJS :

   service.getAll()
    .then(onSuccess) // I process the data
    .catch(onError) // I do whatever needed to notify anyone about the issue
    .finally(onFinally); // I stop the loading spinner and other stuff

Now in Angular 6/RxJS 6, I don't understand why everything is so complicated and look not right.

I could find two ways of doing the same thing than above:

  1. The full pipe

    this.service.getAll()
        .pipe(
            map((data) => this.onSuccess(data)),
            catchError(error => of(this.handleError(error))),
            finalize(() => this.stopLoading())
        )
        .subscribe();
    

Since we have to use pipe for the finalize, I might as well use pipe for everything, I think it's better practice to have everything in the same order. But now we have to throw something, called "of" (not very easy to understand) and I don't like that.

  1. The half pipe So I try another idea, with only the pipe I need (finalize) and I keep the subscribe callbacks.

    this.service.getAll()
    .pipe(
        finalize(() => this.stopLoading())
    )
    .subscribe(
        (data) => this.onSuccess(data),
        (error) => this.handleError(error)
    );
    

But, well. Isn't it a little bit backward? We still have callbacks without actual names, and we finalize before reading the processing and the error. Weird.

So there is something I definitely don't understand. And I can't find anything related to this basic question online. You either have someone who wants "success and finally", or "success and error" but noone wants the 3 of them. Maybe I'm too old and I don't understand the new best practice about that (if so, please educate me!).

My need is simple:
1. I want to process the data I get from a service
2. I want to get the error in order to display to the user
3. I want to stop the loading spinner I just started before the call, or make another call once the first one is complete success or error (I really want a finally)

How do you handle your basic HTTP call with observable?

(I don't want any .toPromise, please, I want to understand how to do with the new stuff)

Answer

martin picture martin · Jun 28, 2018

I think there's one key misunderstanding:

You either have someone who wants "success and finally", or "success and error" but none wants the 3 of them.

This isn't entirely true. Each Observable can send zero or more next notifications and one error or complete notification but never both. For example when making a successful HTTP call you'll have one next and one complete notification. On error HTTP request you'll have only one error notification and that's all. See http://reactivex.io/documentation/contract.html

This means you'll never have an Observable emitting both error and complete.

And then there's the finalize operator. This operator is invoked when disposing the chain (which includes plain unsubscribing as well). In other words it's called after both error and complete notifications.

So the second example you have is correct. I understand it looks weird that you include finalize before subscribing but in fact each emissions from the source Observable goes first from top to bottom where it reaches subscribers and there if its error or complete notification it triggers dispose handlers bottom up (in opposite order) and at this point finalize is called. See https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subscriber.ts#L150-L152

In your example using finalize is the same as adding the dispose handler yourself into a Subscription objects.

const subscription = this.service.getAll()
  .subscribe(
    (data) => this.onSuccess(data),
    (error) => this.handleError(error)
  );

subscription.add(() => this.stopLoading());