You have probably heard many times that it’s a good practice to unsubscribe from an Observable
after you subscribe to it in your Angular application. But is it always the case? Do you really need to do it?
The truth is that some Observables don’t need you to unsubscribe from them. Let’s understand which ones so that we don’t write useless code, creating overhead.
Why do we need to unsubscribe?
The difference between a Promise
and an Observable
is that the first just emits one value and completes, while the latter opens a stream of events and keep listening until we unsubscribe from it. That subscription keeps going even after the Angular component doesn’t exist anymore.
Imagine that we have a route /user
with a UserComponent
where we subscribe to an Observable
that emits an event every time the user object changes. Something like this:
ngOnInit() { this.userService.userChanged.subscribe(user => { console.log(user); }); }
Now, if we enter the /user
route, the component will subscribe to userChanged
. Let’s pretend that we go back to the application home page and then we navigate again to /user
. If we are navigating from a menu and we are not refreshing the page, every time we enter the UserComponent
, a new subscription is created.
What do you think will happen if we trigger that Observable
, sending an event to userChanged
? How many console.log
will you see? As many as the number of times we entered the page! Every time, in fact, a new subscription is created, without destroying the previous ones. This will create memory leaks in your app and it will affect its performances.
The solution
As you probably already know, the solution is unsubscribing from every Observable
that we subscribed to, and we do that in the ngOnDestroy
method, called just before the component gets destroyed.
There are a few ways for accomplishing it.
You can save the Subscription
into a property in the component:
private userSubscription: Subscription; ngOnInit() { this.userSubscription = this.userService.userChanged.subscribe(user => { console.log(user); }); } ngOnDestroy() { this.userSubscription.unsubscribe() }
You can collect all the subscriptions into an array:
private subscriptions: Subscription[] = []; ngOnInit() { this.userSubscription.push( this.userService.userChanged.subscribe(user => { console.log(user); }); ); this.userSubscription.push( this.anotherService.something.subscribe(); ); } ngOnDestroy() { this.userSubscription.forEach(s => s.unsubscribe()); }
You can use the takeUntil
pipeable operator with a Subject
:
private unsubscribe$ = new Subject(); ngOnInit() { this.userService.userChanged.pipe( takeUntil(this.unsubscribe$) ).subscribe(user => { console.log(user); }); } ngOnDestroy() { this.unsubscribe$.next(); this.unsubscribe$.complete(); }
In this case, the takeUntil
operator will complete the observable when the unsubscribe$
Subject
emits its value. Note that at line 13 we also complete the Subject
, in order not to leave it unsubscribed.
Finally you can use the async
pipe:
// in the component: user$: Observable<User>; ngOnInit() { this.user$ = this.userService.userChanged; } // in the template: <div *ngIf="user$ | async as user">{{user.name}}</div>
The async
pipe automatically subscribes to the Observable
and unsubscribes when the component gets destroyed.
When do we need to unsubscribe?
Ok, we’ve learned how to unsubscribe, but what kind of Observables actually need us to do it?
Almost every Observable
, except for a few ones managed by Angular:
- The
HttpClient
Observables: since they will only return one value, they are implemented for completing just after they fire the first event, like a Promise. - The
ActivatedRoute
Observables likeroute.params
: they are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injectedActivatedRoute
dies with it.
Also, you don’t need to unsubscribe from Observables that you subscribe to in the AppComponent
, as it never gets destroyed.
You always have to unsubscribe from:
- Any
Observable
orSubject
you created manually. FormGroup
observables likeform.valueChanges
andform.statusChanges
- Observables of
Renderer2
service likerenderer2.listen
In case you have doubts, you can always unsubscribe, just in case.