4

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')));
        }
wentjun
  • 40,384
  • 10
  • 95
  • 107
Ka Tech
  • 8,937
  • 14
  • 53
  • 78

5 Answers5

4

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.
wentjun
  • 40,384
  • 10
  • 95
  • 107
4

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();
  }
}
kvetis
  • 6,682
  • 1
  • 28
  • 48
  • Thanks for the solution. One quick is the SubSink utility necessary. Can't we just use Subscription property type? ie private subs: Subscription. And then on destroy this.subs.unsubscribe()? – Ka Tech Oct 19 '19 at 23:48
  • You can have `subs: Subscription[] = []`, call `this.subs.push(...subscribe(...))` and on destroy call `subs.forEach(s => s.unsubscribe())`. The subsink just simplifies it a little. – kvetis Oct 21 '19 at 10:24
  • @kvetis: Don't you have a typo in code? `MonoTypeOperatorFunction`. And also in `SampleComponent` we must have a super call in constructor. – k.vincent Nov 08 '19 at 11:47
  • @k.vincent thanks for noticing. Fixed the typo. Not sure what you mean by the `super` call in the `SampleComponent ` since it does not have a constructor nor it inherits from anything. – kvetis Nov 08 '19 at 13:20
  • @kvetis: You're welcome. super() - I see if it doesn't have a constructor, no need. But I think in nearly all of the cases, we would have a constructor, and then we'll get an error if it's not called within the constructor as: `super()` – k.vincent Nov 08 '19 at 13:28
0

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(...)
Patricio Vargas
  • 5,236
  • 11
  • 49
  • 100
  • Thanks but what is the type definition of yourObservableName$? If I define it as yourObservableName$=Observable I get the following error:Type 'Observable' is not assignable to type 'Observable'. – Ka Tech Oct 19 '19 at 06:29
  • All good figured it out, get must be made as this.httpClient.get – Ka Tech Oct 19 '19 at 07:39
  • 1
    Pato in your suggested solution how would I pass a param to httpClient in Service.ts from my component.ts? – Ka Tech Oct 19 '19 at 23:44
  • if you are passing parameters then I will do it in a function like you are doing. – Patricio Vargas Oct 20 '19 at 00:40
  • Thanks also in service.ts for me to get it working in the function i also have to add yourObservableName$.subscribe() in order to call the http request – Ka Tech Oct 20 '19 at 03:18
0

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.

-1

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!