RxJS in Angular: When To Subscribe? (Rarely)

(Rarely)Michael LortonBlockedUnblockFollowFollowingJan 8Ben Lesh has a terrific article called “Don’t Unsubscribe”, telling you why you are better off not unsubscribing.

In that spirit, I wanted to title this article “Don’t Even Subscribe”, but the truth is, there are a few places where you have to subscribe, so I settled for the less-emphatic title you see.

A longer answer to the question “When To Subscribe?” is, “Only when you absolutely have to.

” If you don’t subscribe, you don’t have to unsubscribe.

So, when do you absolutely have to subscribe?Think of it like a magazine: if you aren’t the one reading the magazine, it’s a waste for you to subscribe.

In ServicesSo far as I can tell, you don’t have to subscribe to Observables inside services.

In general, services exist to provide data to other entities.

A component or a directive asks for data, and the service returns an Observable that will eventually supply that data.

There is no reason for the service itself to subscribe.

Of course, a service might call another service, and get back an Observable.

You might consider subscribing to it, but remember: that service only made that call because it was itself called.

At the bottom of the calling chain, there must be a UI element, like a component or directive — and if the service returns the Observable, the component or directive can do the subscription.

The only case where the service might subscribe is if a method is called with an Observable and the service method’s job is to do something with the data that the Observable eventually emits.

Even in that case, it seems likely that the best practice would be, instead of subscribing explicitly, create from that Observable a new Observable meaning “I’m done” and requiring the calling function to subscribe to that.

Say a service is given an Observable and the job is, collect all the data that Observable ever emits and write it to another service.

Yes, that could be done by subscribing the Observable, and upon completion, write the data.

But instead, it could be written as:Now the calling function is (properly) responsible for subscription.

Programmers have a tendency to want to write services that the user can “fire and forget”, by returning an Observable that is already hot, already working, but experience has shown this is not ideal.

In some cases, “fire and forget” is not reasonable, and then you have some methods returning hot Observables and other ones returning cold ones, leading to confusion.

Better to bite the bullet and just insist that subscribing is always the calling function’s responsibility.

Is there some case where either returning an Observable is not feasible, or the service wants to retain the subscription, perhaps for cancellation?.I don’t know for sure, but I have not seen such a case, and I have not been able to contrive one.

The answer to the question “When To Subscribe In Services?” seems to be, “Only when you absolutely have to — and you almost never ‘absolutely’ have to.

”In ComponentsIn the real world, a lot of components are misusing subscription.

There’s a lot of code out there that unfortunately looks like this:What you should notice first is that the code leaks.

The subscription is never unsubscribed, so if the Observable does not complete on its own, that whole component, and its template and all their associated objects, will live in memory forever.

You could fix it up, like so:(See here for more discussion of the takeUntil() idiom.

)This version does not leak outright, but it is still somewhat over-complicated.

It is carefully copying data into the component, which does not care.

Moreover, now the component’s state has changed, but the template still has to be updated.

If you are using the CheckAlways change detection strategy, that change will be noticed automatically, but CheckAlways is slow and expensive; if you using the (otherwise preferable) OnPush change detection strategy, you have to remember to manually call markForCheck() on the ChangeDetectorRef.

Generally, the pattern of “subscribe and in the subscription function, copy data into the state of the component” is not a healthy one.

It gets you unnecessarily involved in change detection and, relevant to this article, it’s an unnecessary subscription.

Better (more performant, clearer) to let Angular handle the Observable directly using the async pipe:(See here for more discussion of replacing subscriptions with async.

)So, is the answer to the question “When To Subscribe In Components?” also “Only when you absolutely have to — and you almost never ‘absolutely’ have to”?Sadly, no.

There are at least two cases where you should explicitly subscribe to Observables in components and directives.

First is when the Observable makes a change to the outside world.

Most commonly, this is done by issuing a POST (or DELETE or PUT) through HTTP, meant to update the backend.

Second is when the component itself — and not Angular via a template — is consuming the data.

You see this when a component opens a modal dialog or send a message to the user, like a popup or a “snackbar”.

Luckily, those two cases usually happen together: the user asks for some permanent change, and the code makes it and then tells the user that it was made.

Here is a very simple case:There is a little bit of controversy here.

This code changes “the outside world” in two place — the logButtonPress(), where it writes to the log, and the snackbar, where it tells the user what has happened — which is why it needs a subscription at all, but the first change doesn’t occur in the subscription, it occurs in the concatMap(), conflicting with the general rule that functions inside operators should be “pure” and not change the outside world like that.

The alternative, however, is to have nested subscriptions: subscribe to the button press and in the function, invoke ogButtonPress() and subscribe to that, invoking the snackbar in that inner subscription.

The collective wisdom is that the first method is less-bad, at least in part because there is no reason that the nesting would stop at two levels: maybe you have several asynchronous, mutating changes that have be made, and several UI updates that have to be made when they complete.

However you do it, though, you will have to subscribe at least once.

Unfortunately for my catchy title, subscribing is more widely necessary than unsubscribing, but still, only do it when it is, in fact, necessary.

.

. More details

Leave a Reply