Ka Tech
Ka Tech

Reputation: 9467

Angular observables - Do I need unsubscribe if no subscription?

I am using latest angular 8 and am new to the concept of observables. Question I have if I am directly calling an observable and not apply it to a subscription variable, do I still need to unsubscribe. Below are the scenarios I would like to know if I need to unsubscribe on? Many thanks in advance

Scenario 1 - Calling a httpService from a component:

Service - httpService

     getContactsHttp(){
         let headers: any = new HttpHeaders(this.authService.getHeadersClient());
         return this.httpClient.get('/contacts', {headers: headers})
          .pipe(timeout(this.authService.getTimeoutLimit('normal')));
        }

Component - Calling getContactsHttp and sorting response

getContacts() {
 this.httpService.getContactsHttp().subscribe((data:any[])=>{
  this.records = this.sortData(data)
 })
}

Scenario 2 - on an observable susbcribed in a component

contacts$: new Subject<any[]>;

ngOnInit() {
  this.getContacts();
  this.contacts$.subscribe((data:any[])=>{
    this.records = this.sortData(data);
  })
}

getContacts() {
    this.httpService.getContactsHttp().subscribe((data:ContactSearch[])=>{      
      this.contacts$.next(data);
    })
  }

Service - httpService

     getContactsHttp(){
         let headers: any = new HttpHeaders(this.authService.getHeadersClient());
         return this.httpClient.get('/contacts', {headers: headers})
          .pipe(timeout(this.authService.getTimeoutLimit('normal')));
        }

Upvotes: 4

Views: 6166

Answers (5)

kvetis
kvetis

Reputation: 7351

1) Generally, you don't need to unsubscribe when calling an http call directly. Even if the component get's destroyed, the overhead with the subscription finishing after the destruction is insignificant. You'd need to unsubscribe here if switching your components rapidly. Also unsubscribing cancels the http request, so if that's desired, then unsubscribe.

Unsubscribing does not do any harm. If you're not sure, always unsubscribe.

2) You do need to unsubscribe when subscribing to an observable that does not complete when your compoment gets destroyed. Otherwise this would cause a memory (and performance) leak. Because the observable itself holds a reference to the subscription and the subscription holds the reference to the component, the component will never get cleared from the memory and the action described in the subscription will be running until the observable completes, which in your case is never. That will happend for every instance of your component.

Solutions

I'll share two popular options on simplifying the burden of unsubscribing. Expanding on the @amanagg1204 answer, you could create a base component from which you'd extend all of your future components. You can have a custom operator in it. There is one downside to that - you always have to call super.ngOnDestroy() if you need to use ngOnDestroy in your component.

import { OnDestroy } from "@angular/core";
import { Subject, MonotypeOperatorFunction } from "rxjs";
import { takeUntil } from "rxjs/operators";

export abstract class UnsubscribeComponent implements OnDestroy {
  protected destroyed$: Subject<void> = new Subject();

  ngOnDestroy(): void {
    this.destroyed$.next();
    this.destroyed$.complete();
  }

  takeUntilDestroyed<T>(): MonoTypeOperatorFunction<T> {
      return takeUntil(this.destroyed$);
  }
}

export class Component extends UnsubscribeComponent {
   ngOnInit() {
      this.contacts$.pipe(
              this.takeUntilDestroyed(),
          ).subscribe((data:any[])=>{
          this.records = this.sortData(data);
      });
   }

  // WARNING - if you declare your ngOnDestroy in the component
  ngOnDestroy() {
     // DO NOT FORGET to call this
     super.ngOnDestroy();
     doYourStuff();
  }

}

Other option (my prefered) is not to have a parent abstract class (although it could also be implemented like that), but to use an utility called subsink (npm i subsink --save)

import { SubSink } from 'subsink';

export class SampleComponent implements OnInit, OnDestroy {
  private subs = new SubSink();

  ngOnInit(): void {
    // Just put it into sink.
    this.subs.sink = this.contacts$.subscribe((data:any[])=>{
      this.records = this.sortData(data);
    });
    // call repeatedly
    this.subs.sink = this.otherService$.subscribe((data:any[])=>{
      this.things = this.sortData(data);
    });
  }

  ngOnDestroy(): void {
    // this will unsubscribe all
    this.subs.unsubscribe();
  }
}

Upvotes: 4

amanagg1204
amanagg1204

Reputation: 92

As many have already pointed out, http returns a Cold Observable but you should still unsubscribe from the observable.I will suggest a better way of managing unsubscription so that you don't have to manually add a ngOnDestroy() lifecycle hook every time. I would start by creating an unsubscribe component as below

import { OnDestroy } from "@angular/core";
import { Subject } from "rxjs";

export abstract class UnsubscribeComponent implements OnDestroy {
  protected destroyed$: Subject<void> = new Subject();

  ngOnDestroy(): void {
    this.destroyed$.next();
    this.destroyed$.complete();
  }
}

And then in extend it in every component class declaration (wherever required)

export class ABC extends UnsubscribeComponent

Within the constructor call super()

constructor(your dependencies) {
  super()
}

and finally with your subscriptions, do something like below

this.obs$.pipe(takeUntil(this.destroyed$)).subscribe(() => {
  // some logic here
})

Another thing is takeUntil should be last operator in the pipe. Hope this helps you.

Upvotes: 0

Patricio Vargas
Patricio Vargas

Reputation: 5522

Yes always unsubscribe. You have multiple ways of unsubscribing, which are the following:

-the use takeUntil()

-the take(1)

-unsubscribe() in the ngOnDestroy()

-using the async pipe

Yes, the httpClient returns a cold observable, but this question is going to explain you why you should still unsubscribe. (take a look to the second answer with highest votes)

Is it necessary to unsubscribe from observables created by Http methods?

https://blog.angularindepth.com/why-you-have-to-unsubscribe-from-observable-92502d5639d0

On the side note:

1) Never subscribe in the service. We are in the Angular world and we need to think in a reactive way, buy subscribing in the service prevents you from making use of that observable in case you want to combine it with something else.

2) Start using the declarative approach when working with observables.

3) Stop using any, it's not good practice. Make the use of classes and interfaces, by doing so your code will be more readable.

Benefits from declarative approach: -Leverage the power of RxJs observables and operators -Effectively combine streams -Easy share observables -Readily react to user action

You maybe wondering how does a declarative approach looks like?

Instead of having methods for returning observables, you are going to do this.

SERVICE.TS

  yourObservableName$ = this.httpClient.get('/contacts', {headers: headers})
              .pipe(timeout(this.authService.getTimeoutLimit('normal')));

COMPONENT.TS

this.httpService.yourObservableName$.subscribe(...)

Upvotes: 0

wentjun
wentjun

Reputation: 42596

Short answer, yes, you still unsubscribe to your observables in your component to avoid subscription leaks. One of my preferred ways of doing so would be to make use of the takeUntil() operator.

This is how you can use it in your component.

private unsubscribe: Subject<void> = new Subject();

ngOnDestroy() {
  this.unsubscribe.next();
  this.unsubscribe.complete();
}

getContacts() {
  this.httpService.getContactsHttp()
    .pipe(
      takeUntil(this.unsubscribe),
    ).subscribe((data:ContactSearch[])=>{      
      this.contacts$.next(data);
    });
 }

As explained by Brian Love,

  • First, we import the takeUntil() operator as well as the Subject class.
  • Next, we define a private instance property named unsubscribe, which is a Subject.
  • We also create a new instance of Subject, defining the generic type as void. We use the takeUntil() operator in the pipe() method before invoking subscribe(), providing the unsubscribe observable.
  • In the ngOnDestroy() lifecycle method we emit a next() notification, and then complete() the unsubscribe observable. The subscription is now complete, and we have immediately unsubscribed when the ngOnDestroy() method is invoked during the lifecycle of our component.

Upvotes: 4

Chirag Bhatia
Chirag Bhatia

Reputation: 79

Yes, its a good practice to unsubscribe all the subscriptions in ngOnDestroy lifecycle method, you can do it by taking a reference of each subscription in class level variable and then unsubscribe them manually!

Upvotes: -1

Related Questions