When should I store the Subscription
instances and invoke unsubscribe()
during the ngOnDestroy
life cycle and when can I simply ignore them?
Saving all subscriptions introduces a lot of mess into component code.
HTTP Client Guide ignore subscriptions like this:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
In the same time Route & Navigation Guide says that:
Eventually, we'll navigate somewhere else. The router will remove this component from the DOM and destroy it. We need to clean up after ourselves before that happens. Specifically, we must unsubscribe before Angular destroys the component. Failure to do so could create a memory leak.
We unsubscribe from our
Observable
in thengOnDestroy
method.
private sub: any;
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = +params['id']; // (+) converts string 'id' to a number
this.service.getHero(id).then(hero => this.hero = hero);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
TL;DR
For this question there are two kinds of Observables - finite value and infinite value.
http
Observables produce finite (1) values and something like a DOM event listener Observable produces infinite values.If you manually call
subscribe
(not using async pipe), thenunsubscribe
from infinite Observables.Don't worry about finite ones, RxJs will take care of them.
Sources:
I tracked down an answer from Rob Wormald in Angular's Gitter here.
He states (I reorganized for clarity and emphasis is mine):
Also he mentions in this YouTube video on Observables that "they clean up after themselves..." in the context of Observables that complete (like Promises, which always complete because they are always producing one value and ending - we never worried about unsubscribing from Promises to make sure they clean up XHR event listeners, right?)
Also in the Rangle guide to Angular 2 it reads
When does the phrase "our
Observable
has a longer lifespan than our subscription" apply?It applies when a subscription is created inside a component which is destroyed before (or not 'long' before) the Observable completes.
I read this as meaning if we subscribe to an
http
request or an Observable that emits 10 values and our component is destroyed before thathttp
request returns or the 10 values have been emitted, we are still OK!When the request does return or the 10th value is finally emitted the Observable will complete and all resources will be cleaned up.
If we look at this example from the same Rangle guide we can see that the subscription to
route.params
does require anunsubscribe()
because we don't know when thoseparams
will stop changing (emitting new values).The component could be destroyed by navigating away in which case the route params will likely still be changing (they could technically change until the app ends) and the resources allocated in subscription would still be allocated because there hasn't been a completion.
In this video from NgEurope Rob Wormald also says you do not need to unsubscribe from Router Observables. He also mentions the
http
service andActivatedRoute.params
in this video from November 2016.The Angular tutorial, the Routing chapter now states the following:
Here's a discussion on the GitHub Issues for the Angular docs regarding Router Observables where Ward Bell mentions that clarification for all of this is in the works.
I spoke with Ward Bell about this question at NGConf (I even showed him this answer which he said was correct) but he told me the docs team for Angular had a solution to this question that is unpublished (though they are working on getting it approved). He also told me I could update my SO answer with the forthcoming official recommendation.
The solution we should all use going forward is to add a
private ngUnsubscribe = new Subject<void>();
field to all components that have.subscribe()
calls to Observables within their class code.We then call
this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
in ourngOnDestroy()
methods.The secret sauce (as noted already by @metamaker) is to call
takeUntil(this.ngUnsubscribe)
before each of our.subscribe()
calls which will guarantee all subscriptions will be cleaned up when the component is destroyed.Example:
Note: It's important to add the
takeUntil
operator as the last one to prevent leaks with intermediate Observables in the operator chain.More recently, in an episode of Adventures in Angular Ben Lesh and Ward Bell discuss the issues around how/when to unsubscribe in a component. The discussion starts at about 1:05:30.
Ward mentions "right now there's an awful takeUntil dance that takes a lot of machinery" and Shai Reznik mentions "Angular handles some of the subscriptions like http and routing".
In response Ben mentions that there are discussions right now to allow Observables to hook into the Angular component lifecycle events and Ward suggests an Observable of lifecycle events that a component could subscribe to as a way of knowing when to complete Observables maintained as component internal state.
That said, we mostly need solutions now so here are some other resources.
A recommendation for the
takeUntil()
pattern from RxJs core team member Nicholas Jamieson and a TSLint rule to help enforce it: https://ncjamieson.com/avoiding-takeuntil-leaks/Lightweight npm package that exposes an Observable operator that takes a component instance (
this
) as a parameter and automatically unsubscribes duringngOnDestroy
: https://github.com/NetanelBasal/ngx-take-until-destroyAnother variation of the above with slightly better ergonomics if you are not doing AOT builds (but we should all be doing AOT now): https://github.com/smnbbrv/ngx-rx-collector
Custom directive
*ngSubscribe
that works like async pipe but creates an embedded view in your template so you can refer to the 'unwrapped' value throughout your template: https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697fI mention in a comment to Nicholas' blog that over-use of
takeUntil()
could be a sign that your component is trying to do too much and that separating your existing components into Feature and Presentational components should be considered. You can then| async
the Observable from the Feature component into anInput
of the Presentational component, which means no subscriptions are necessary anywhere. Read more about this approach here.