Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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')));
        }
like image 419
Ka Tech Avatar asked Mar 04 '23 07:03

Ka Tech


2 Answers

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.
like image 57
wentjun Avatar answered Mar 09 '23 00:03

wentjun


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();
  }
}
like image 44
kvetis Avatar answered Mar 09 '23 00:03

kvetis