Briebug Blog

Sharing our thoughts with the community

When should I unsubscribe my Subscriptions in Angular?

When should I unsubscribe my Subscriptions in Angular?


Once you've been working with Angular for a little while, you may begin to wonder about observables, Subscription and, more importantly, when and how to unsubscribe from a subscription. These are an excellent questions and lead into one of the most fundamental best practices with Angular.


Never Subscribe

First things first: Never subscribe! If there was ever a best practice to follow with observables in Angular, this would be it. If you can avoid it, manually subscribing to any Observable should be avoided at all costs. You may find that some times they are unavoidable and in those cases you can use some of the recommendations later on in this article for dealing with their cleanup.


For most cases, avoiding any direct subscriptions and using frameworks to handle subscriptions for you is the best option. Angular itself offers a couple of options to manage subscriptions and if you use NgRx (And you should!) there is an additional option. When relying on frameworks, they handle subscribing, unsubscribing and in some cases error handling at a basic level for you.


Completion

A fundamental aspect of observables is that when they complete, any subscriptions are automatically unsubscribed. As such, if you know an observable will complete then you do not need to worry about cleaning up any subscriptions.


This is true for any observable created with of, from, or many of the built in observable sources in Angular such as HttpClient methods or ActivatedRoute properties. You can call these and simply subscribe to their observables without any further concerns. There is no standard convention that indicates what observables may complete, or when, so it is up to the developer to do the necessary research. Any observable that is guaranteed to complete is also guaranteed to clean up its subscribers.


Not all observables complete. Some persist, emitting value after value, in which case any subscription will need to be cleaned up.



The Async Pipe

The first and most common solution to handling subscriptions is the async pipe in Angular. Rather than subscribing to observables in your components and setting properties on your class to data from the observable:


Instead: create a property for the Observable! Then simply subscribe to that observable in your template using async:



By using the async pipe, you no longer have a subscription to worry about. At all. Period. Problem solved! The async pipe cleans up its own subscriptions when and as necessary, taking that responsibility off your plate. Now this should be your first choice, and will work for most scenarios, however it is not always an option so we'll go over some others.



Container/Presenter

Another thing to note here is: sometimes you may need to work with multiple observables. In these cases, it helps to create presentational components to handle the rendering of the data and use a container component to handle the observables.


A container component would be much the same as what we started with above:


While a presentational component will handle the actual rendering of the data, accepting said data as inputs (and potentially exposing outputs to handle things like selections of data):



First thing to note here is how responsibilities are separated. The container deals with observables and getting data. The presenter simply displays data. The presenter doesn't even do any logic to find the matching CustomerOrderCount object for each customer; instead it delegates that to a custom pipe called orderCount (which is also a best practice, we'll reserve pipes for a future article.)


This segregation is key to using observables and the async angular pipe effectively. An Observable itself is not data: it is a thing that represents potential data, at some point in time and perhaps versions of data as it changes over time. Observables must be "unwrapped" in order to get the data they represent. This is what the async angular pipe does: It unwraps observables in the template and exposes the data they have emitted.



Beyond the Template

The async pipe is only an option within the template. Not all code in an Angular application will, or even should, exist within components and templates. There are good arguments to be made for taking most of such code out of the components and templates, as they have their responsibilities, and they shouldn't be conflated with business behaviors. Components do UI.


Even when an observable belongs in the UI, such as say Controls on a FormGroup, and their value changes? Some times these things simply cannot be handled within the template and must be handled within the component.



The Subscription

So what do you do when you cannot use the async pipe? There are a couple of options. The first and simplest is to use a Subscription to collect all of the subscriptions you create, allowing you to clean them up later at an appropriate time.


The Subscription object within RxJs is, interestingly, not just representative of a single subscription to a stream. It is also representative of a Subscription aggregate that contains other subscriptions. This allows it to be used as a single point of control for any number of subscriptions.




The Signal

Another way to unsubscribe, and perhaps a more elegant way, is to use signals. Signals are a way to use other streams to control streams you may have subscribed to. This actually goes whether you subscribe to them directly, or not. So they work with the async angular pipe as well.


Signals are achieved with the takeUntil operator in the pipe and any other observable. The other observable may be a Subject or may be another Observable; anything that emits next.



The more you use reactive programming techniques, and the more you leverage RxJs, the more you will find you are using the observable pipe. As such, it is a sound practice to try and use the pipe as much as possible, so using takeUntil becomes a solid option for managing subscriptions. This is doubly true since it works with async in the templates as well.



Using NgRx

I mentioned early in this article that you should rely on frameworks to handle subscriptions for you. While Angular provides either observables that automatically complete or the async pipe, it is not the only commonly used framework that can help us here. NgRx is another framework that can manage subscriptions for us.



Effects

With NgRx, we are able to move the bulk of our business logic out of components, where it is debatable whether it belongs in the first place, and into more appropriate facilities. Facades, services...and effects. Effects are the primary way of handling logic within NgRx. They allow us to observe streams of actions and react to them. We do not need to subscribe to any effect nor do you need to manually unsubscribe. That is handled internally by the NgRx Effects module; once again taking that burden out of our hands.



Note here that we have moved the responsibility of loading customer data out of the component and into an effect that handles an action. We do not subscribe anywhere, we simply describe what we need to be done, and perhaps dispatch another action, or execute some side effect (i.e. showing an error toast.)



Selectors

When using NgRx and state to store application data, you will find your normal course for actually getting data to display in your UI components will be selectors. In NgRx, a selector is a memoized function that retrieves data from state in the NgRx Store. Selectors are relatively simple and in a well designed NgRx application you will likely have a lot of them, as most data should end up in the store, and selectors present the most efficient means of retrieving data from state.


Using selectors is basically the same as using a data service, which we covered first in this article. Selectors are often commonly used throughout an app and it can be helpful to wrap them in facade classes to support clean reuse. Facades may also expose reusable action-related behavior.


Once you have facades, they may be exposed to your templates from your components and used directly with the async pipe:



Because we can select data from the NgRx store with selectors directly in the template, and unwrap those selections with the async angular pipe, we once again do not need to worry about unsubscribing from these streams. Angular will handle the necessary subscribing and unsubscribing for us as components are constructed and destroyed.



Wrapping Up

Subscriptions are a fact of life with RxJs observables. There is no avoiding them; without a subscription somewhere, the data streaming through them cannot be properly used. Managing subscriptions is the key. Your first course of action with observables should be to find a way to use them without explicit subscriptions. Use frameworks to handle subscriptions for you, whenever possible. Leverage RxJs factory functions and operators that complete, such as: of and from, or takeUntil, take, first, etc.


In the event that you must subscribe, be aware of the nature of your observable. Completing observables will clean up subscriptions on their own. Any observable that does not naturally complete will require explicit management of any subscriptions you make to it. Always clean up any manually created subscriptions to avoid leaking subscriptions in your reactive apps.

View Details
- +
Sold Out