I've seen a lot of discussions123 about necessity of unsubscribing from observables during Angular component's destroying. The rule of thumb which I picked up is: when you explicitly subscribe()
to an infinite observable - you are responsible for unsubscribing from it, otherwise you might end up with a memory leak due to subscriptions, which remain alive after the component is destroyed.
Of course, it doesn't relate to finite observables, such as HttpClient
calls, which complete right after the request is finished. Also we are safe with async
pipes, as Angular does unsubscription for us in this case.
There are various ways of unsubscribing: we can group all subscriptions into one Subscription
object and then explicitly unsubscribe in ngOnDestroy
or we can create a Subject
, which completes on component destroy and use takeUntil
operator with every subscription. Angular 16 made it even easier by introducing injectable DestroyRef
and takeUntilDestroyed
operator4.
Nevertheless, is it OK to mindlessly add these safety guards to every subscription?
I realized that in many cases when the target observable is a property (direct or indirect) of the same component - there are no problems with garbage collection.
Take a look at this stackblitz. For illustrative purposes FooComponent
reserves a 10MB array which is referenced in the subscription to valueChanged
observable of reactive form control. We can create and destroy the component several times and eventually see that the component instance, form model, 10MB array and subscription - everything is garbage collected. I used FinalizationRegistry
to see it even more clearly.
Subscription to URL params from injected ActivatedRoute
instance? It is cleaned up too. Angular Material paginator/sort as view children of the component? No problem either.
This does not mean that memory leaks are impossible. If your service spawns an infinite observable and you don't unsubscribe in your component - you will have a destroyed component zombie object in the heap. Until the service is destroyed, which is likely equal to 'until you quit the app'.
Things also may get more complicated if we do some processing of observable data in between, there may be a lot of examples.
However, do we need to blindly follow the rule and populate our code with takeUntil
or whatever just for the sake of clarity? Can we think a little bit deeper and keep the code cleaner where possible? Or is it better to keep the habit and insert safeguards everywhere, just to be sure that we didn't miss a possible problem? What do you think?
Just after I have written this post my friend guided me to this publication by Daniel Glejzner, which provides killer arguments in favor of "keeping the habit" even in case of HttpClient
observables.
-
https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription ↩
-
https://stackoverflow.com/questions/70093116/do-i-need-to-unsubscribe-from-an-angular-observable ↩
-
https://stackoverflow.com/questions/61147869/do-i-need-to-unsubscribe-manually-angular-8 ↩
-
https://angular.io/api/core/rxjs-interop/takeUntilDestroyed ↩