To avoid memory leaks in my Angular app i'm using the following well-known pattern to unsubscribe from Observables:
unsubscribe = new Subject();
ngOnInit() {
this.myService.getStuff()
.pipe(takeUntil(this.unsubscribe))
.subscribe(result => {
// processing the result
});
}
ngOnDestroy() {
this.unsubscribe.next();
}
This seemingly works fine, but in some examples i've noticed that complete()
is also called on the Subject
in addition to next()
:
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete(); // like this
}
Is calling complete()
necessary here? If so, why? What are the consequences of not calling complete()
in this scenario?
The takeUntil operator returns an Observable that emits value from the source Observable until the notifier Observable emits a value. When the notifier emits a value, the TakeUntil completes the Source observable.
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.
We can simply call the unsubscribe() method from the Subscription object returned by the subscribe() method in the ngOnDestroy() life-cycle method of the component to unsubscribe from the Observable. There is also a better way to unsubscribe from or complete Observables by using the takeUntil() operator.
takeUntil subscribes and begins mirroring the source Observable. It also monitors a second Observable, notifier that you provide. If the notifier emits a value, the output Observable stops mirroring the source Observable and completes.
Let's see why you need to unsubscribe first.
Very simplified: Observable instance is holding an array of all subscriptions, which means every callback you have in your subscribe
will be held in this array. This is bad news for Component because while it is referred from those functions it cannot be garbage-collected. I talk about these functions:
ngOnInit() {
this.myService.getStuff()
.subscribe(
result => null, // this function will be stored in Observable
error => null, // and this
() => null, // and even this
);
}
and it is applicable to every subscribe
call.
Now you add a pipe .pipe(takeUntil(this.unsubscribe))
(or you can e.g. use my small library that does similar but shorter). In fact, your Observable subscribes to the events of Subject. And, whenever it emits a value, the Observable returned by this.myService.getStuff()
will complete itself. That means all three functions above will be removed from this Observable's subscriptions array and your component is not referred from there anymore.
Problem solved.
All above you need to understand all the why
s you have.
Here we finally come to your question
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
where complete
is unnecessary, but not harming as well. Because the only subscriber to this subject was your Observable from this.myService.getStuff()
(or other Observables from the same component). That means this Subject will refer to nothing else (the only listener is removed and complete
that is supposed to clear all subscriptions is already empty), and as long as only component has reference to the Subject as its property, they both will be collected by garbage collector.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With