Lessons from building large Angular apps: Managing subscriptions

Harijs Deksnis
Passionate People
Published in
8 min readMar 11, 2019

Hi, iam@harijs.dev and I work for PassionatePeople. You can follow me on Twitter as @FrontendNL

This post is based on my talk at AngularNL conference in Amsterdam on 8th of March, 2019. It was well received and I decided to share the contents with a wider audience.

I have decided to split it in several articles to make it more focused and digestible in a single reading session. In this article I will focus on managing multiple subscriptions in your components while writing minimal, yet correct TypeScript code. The second article in the series can be found here.

There are a lot of things you run into while building a large app that are not covered in Angular guide, tutorials or various training videos on sites like egghead.io. These are small know-hows that crystallise by bumping into the same problems over and over again. Opinions might differ how to solve them exactly and I agree that there’s no single right way, but here’s one of my lessons learned. I hope you find it useful :)

Organization of railroad lines and wires. Photo by José Martín Ramírez C

The premise

RxJS observables are a wonderful abstraction over event streams and is a primary citizen in Angular apps. They are used almost for everything. From http requests, route changes to listening and reacting to DOM events and user input. There is, however, one small inconvenience — once we have subscribed to an observable, we must hold on to that subscription until the end of our component’s lifecycle and call .unsubscribe() once we no longer need to react to events coming from that observable. Failing to do so might result in annoying bugs, unwanted side effects and performance degrading memory leaks. Therefore, never forget to unsubscribe to any active subscriptions in ngOnDestroy lifecycle method of your component.

The problem

But that leaves us holding on to all references of the subscriptions we have in a particular component. The classic approach is to add property with its type set to Subscription, initialise subscription in ngOnInit method, and unsubscribe in ngOnDestroy method, which is quite straightforward and simple if you need 1 or maybe 2 subscriptions.

However, that becomes very verbose and cumbersome to write once you have more than a few subscriptions, which is not unusual in large applications. There are at least few components that subscribe to route param changes, query param changes, maybe also resolved data from the route. On top of that, you might need to subscribe to one or more segments from the store and also some DOM changes. This example perhaps is a little bit too contrived, but nothing too far fetched and illustrates my point:

If the use case and logic allows, you might be able to merge that data flow in fewer observables and therefore need less subscriptions. And also, if you need that many subscriptions in one component, you should seriously consider breaking it apart in smaller components. Probably it’s trying to do too much and separation of concerns is in order.

But for the sake of argument, let’s say that there’s nothing wrong with the architecture and this component needs to subscribe to multiple event streams. In a very large and sophisticated application you might have one or few such components. For me the threshold when I start to get annoyed by having too many subscriptions is around 3. And that’s a pretty normal number for any component to have.

Developers are inherently lazy and frown upon doing repetitive tasks or type out unnecessarily many lines. I am no exception and I started looking how can I make it less verbose, yet still correct and achieve all the same functionality goals.

Solution one

Turns out, subscriptions have an add() method which allow adding a child subscription.

Additionally, subscriptions may be grouped together through the add() method, which will attach a child Subscription to the current Subscription. When a Subscription is unsubscribed, all its children (and its grandchildren) will be unsubscribed as well.

This allows to group them hierarchically and at least define less class properties and use less .unsubscribe() calls in your code, reducing the lines of code. That’s definitely an achievement and might result in a code something like this:

Clearly it’s less code and is a correct TypeScript, while also using built in features of RxJS. That must be the way the creators imagined.

There are, however, still a few inconveniences:

  • we must assign a variable for every subscription and explicitly add each to our master subscription;
  • the .add() method does not allow you to add multiple in one go and so you must write more lines to add each of subscriptions;
  • meaningfully grouping subscriptions in a hierarchy might be extra complexity to manage. Which one should be master, which one should be child? How many levels in the hierarchy should there be? What if you need to unsubscribe from some earlier?

From the feedback I have received, a cleaner permutation of this solution would be to instantiate a new Subscription as a master:

And you don’t have to assign separate variables, but instead can put the subscription code right inside the add(). It doesn’t solve all points, but definitely looks cleaner.

I personally do have reservations about putting logic inside add() method. It looks good with short examples, but could get more messy with longer ones. Also it doesn’t allow us to unsubscribe a specific child subscription in isolation if we ever need to.

Another solution

There’s another way I know of now, but won’t go into detail. It’s is one presented by Michael Hladky in his AngularNL talk. He suggested merging multiple observable streams into one by using merge operator and then using .pipe(tap(..), tap(..), ..) to manage different side effects from each. This way we have to call .subscribe() only once at the end and we have only one subscription to manage.

I’m sure his talk will show up on YouTube after a while, or if I manage to find an article describing what he told about, I’ll add a link.

A shorter way

In my talk I presented a way I have been managing my subscriptions in my latest Angular projects, and it has worked pretty well for me. The core idea is exploring what if we didn’t have to assign subscription to a separate variable and call an explicit add for each of them? How would we go about adding more items to an existing collection in a single statement?

The most primitive (and simple) way by using just an existing JavaScript data type is by using an object. If we would have a property which is an empty object, adding new values to it could be done in a single statement.

Then we would be able to just assign new subscriptions to it in a single line, by giving it a distinctive key name to distinguish later and call separately, if needed.

This way we just need a single assignment per subscription, saving some lines and avoiding assigning explicit variables for a short-lived purpose.

What is more, since we are dealing with TypeScript we can reap the benefits of strong typing to prevent misuse of our property and denote that it’s a collection of subscriptions.

Now TypeScript will not allow to assign any other value to subscriptions property and our IDE will be able to provide autocomplete suggestions:

Visual Studio Code autosuggesting callable methods

Alternatively you can choose to define an interface for this type of property:

Unsubscribing

Now when we have shortest possible way to store and add subscriptions, all we need is a convenient way to call unsubscribe on all of them.

If we need to unsubscribe to a particular one, we can always call it explicitly with this.subscriptions[‘route’].unsubscribe(), but how do we unsubscribe from all in one go?

Well, why not iterate over all the values of the object and call unsubscribe on each? That’s what RxJS would do internally anyway if we used the .add() approach.

The most straightforward way we could get an array of subscriptions from our collection would be to to call Object.values on it and pass it to an utility method:

Notice that we spread out our array to pass it as different arguments. This way our utility method can accept one or more subscriptions.

This allows us to use the rest syntax to obtain and assign all the passed in arguments to subs parameter. Using this syntax we can be sure that it will always be an array, no matter what gets passed into this function. And we can call .filter on it and have it keep only subscriptions that can be unsubscribed from.

Drawbacks

The only drawback I can think of is that we have to be careful to use different key names when assigning new subscriptions. If we would accidentally overwrite an existing subscription, it would still remain active, but it would not be unsubscribed when we destroy our component, leading to memory leaks and other problems.

However, it’s not much different when using separate property keys for each subscription. We still had to avoid overwriting them and to remember to unsubscribe from every one.

Conclusion

Subscriptions are this resource that you need to hold on to only while your component is active. Only in a few cases you need a more fine-grained control to unsubscribe earlier. Vast majority of the times we just need to unsubscribe when the component gets destroyed. It’s a fairly trivial matter and we would be fine ignoring it altogether, however, when you are writing many components everything adds up… the boilerplate you need to write and the visual clutter of multiple repetitive lines that serve a similar purpose. What I outlined here in this article is a small trick that allows you to write less code and leave more visual space for actual functionality that matter. After all, often it’s about these small tricks that make one codebase more pleasant to work with than another.

There might be more than one way to tackle this problem, but in my projects I have found this approach to be most concise and still correct TypeScript code with strong typing and IDE autosuggestion support.

I hope there were some insights for you in this article. If you have feedback, please don’t hesitate to leave a comment.

P.S. I intend to write up more articles in near feature based on my conference talk. The second one can be found here.

I am a developer working for PassionatePeople agency in Amsterdam and we build awesome apps with all major frameworks for a variety of clients. You can drop me an email to iam@harijs.dev, follow me @FrontendNL or check out my LinkedIn and Github profile page.

📝 Read this story later in Journal.

🗞 Wake up every Sunday morning to the week’s most noteworthy Tech stories, opinions, and news waiting in your inbox: Get the noteworthy newsletter >

--

--