Reputation: 11599
I have the following code in an angular component to capture the keyup
events and respond when that happens. The user can navigate away from the page, come back and do the same hundreds of times.
fromEvent(this.input?.nativeElement, 'keyup')
.pipe(
pluck<unknown, string>('target', 'value'),
filter((searchTerm: string) => (searchTerm?.length > 2 || searchTerm?.length == 0)),
throttleTime(200),
debounceTime(300),
distinctUntilChanged()
)
.subscribe(search => {
this.setPageIndex();
this.TriggerLoadUsers(search, 'asc', 0, 10);
});
This is another pattern where an explicit assignment of Subscription
is done and then unsubscribed in ngOnDestroy
of angular lifecycle method.
public keyupEventsSub$!: Subscription;
this.keyupEventsSub$ = fromEvent(this.input?.nativeElement, 'keyup')
.pipe(
pluck<unknown, string>('target', 'value'),
filter((searchTerm: string) => (searchTerm?.length > 2 || searchTerm?.length == 0)),
throttleTime(200),
debounceTime(300),
distinctUntilChanged()
)
.subscribe(search => {
this.setPageIndex();
this.TriggerLoadUsers(search, 'asc', 0, 10);
});
this.keyupEventsSub$.unsubscribe();
Subscription
is explicitly assigned, subscribed
and unsubscribed
?Observable
subscription?Upvotes: 0
Views: 1576
Reputation: 85
I think we can experiment if we should explicitly unsubscribe from a subscription.
We can pass the complete callback
as the third parameter in the subscribe
method. If we see that the complete callback
is called immediately after the next callback
then we need not explicitly unsubscribe from that subscription. And at the end of our experiment, we can remove the complete callback
if it was used just for the experimental purpose.
For example, here we can see that the HttpClient.post
request completes after calling the next callback:
this.httpClient.post('https://httpbin.org/post', {testParam: 'testparam'}).subscribe(
(response) => {
console.log('got response', response);
},
(error) => {
console.log('error', error);
},
() => console.log('request/subscription completed')
);
I am not sure if it is a good approach, but seems to be a workable approach. Please correct me if I am wrong. Thank you.
Upvotes: 0
Reputation: 8022
Only Observables
that may never error
or complete
need to be unsubscribed. If you're not sure, it's safer to unsubscribe.
from(promise)
is guaranteed to complete or error.
from(['a','r','r','a','y'])
is guaranteed to complete.
of(...)
is guaranteed to complete.
EMPTY
is guaranteed to complete.
NEVER
shall never complete or fail.
fromEvent(...)
may never complete or fail.
http.get(...)
a well written http client should always complete or fail eventually, but there are some (for various technical reasons) which don't. If you're not sure, unsubscribe.
In general, implicit is better than explicit. There are various operators that will unsubscribe for you when a certain condition is met.
take
,
takeWhile
, and
takeUntil
are the 3 most popular of these. Prefer them over sticking stream.unsubscribe()
in our code somewhere.
Doing so keeps all the logic concerning your observable in one place. Making it considerably easier to maintain/extend as the number of observables that you use grows.
Upvotes: 1
Reputation: 18809
1.) Yes, all subscriptions should be unsubscribed to prevent memory leaks. You don't have to unsubscribe from Http calls or Router events because they are one and done and Angular takes care of it for us but I personally still unsubscribe from all subscriptions.
2.) There is no side effect for using the same pattern for any observable subscription. There are many patterns and I will show at the end.
3.) There is a better pattern and I will go from least preferred to most preferred.
Direct subscription assignment. The disadvantage of this is that you will have many subscription variables for every observable stream so it may get out of hand.
// Direct subscription variable (What you have shown)
// don't put a dollar at the end of subscription variable because
// it is a subscription and not an observable
public subscription!: Subscription;
....
this.subscription = this.observable$.subscribe(...);
...
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
Subscription array: Add every subscription inside of an array.
public subscriptions!: Subscription[];
...
this.subscriptions.push(this.observable$.subscribe(...));
...
ngOnDestroy(): void {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
Async pipe: One of my favorites but can only be used when presenting data in the HTML and not for event listener (in essence meaning react every time an observable emits). When the view is presented, the observable will automatically be subscribed to and once the view is destroyed, the subscription is unsubscribed.
count$ = this.otherObservable$.pipe(map(data => data.count));
...
<h1>{{ count$ | async }}</h1>
Destruction subject: Another one of my favorites and this one is good for subscriptions in the TypeScript class (for event listeners). The beauty of this one is that not too many variables are created and you don't have to deal with an array.
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
....
private destructionSubject$ = new Subject<void>();
...
observable$.pipe(
takeUntil(this.destructionSubject$),
).subscribe(...);
observable2$.pipe(
takeUntil(this.destructionSubject$),
).subscribe(...);
...
ngOnDestroy(): void {
this.destructionSubject$.next();
this.destructionSubject$.complete();
}
There is also another way if all you care about is the first emission and not subsequent emissions:
This can be used for event listeners (react every time this observable emits). This will take the first emission and automatically unsubscribe (the subscription becomes dead).
import { take } from 'rxjs/operators';
....
observable$.pipe(take(1)).subscribe(...);
I hope I answered all of your questions and presented you with good ways to unsubscribe.
Upvotes: 2