wonderful world
wonderful world

Reputation: 11599

Should all RxJS Subscription need to be unsubscribed?

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();
  1. Is there an advantage to following the second pattern where a Subscription is explicitly assigned, subscribed and unsubscribed?
  2. Is there any side effect in using the same pattern for any Observable subscription?
  3. Is there a better pattern where an explicit assignment is not necessary?

Upvotes: 0

Views: 1576

Answers (3)

NAbdulla
NAbdulla

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

Mrk Sef
Mrk Sef

Reputation: 8022

Should all RxJS Subscriptions be unsubscribed?

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.

How to 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

AliF50
AliF50

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

Related Questions