Angular 4+ ngOnDestroy() in service - destroy observable

107,893

Solution 1

OnDestroy lifecycle hook is available in providers. According to the docs:

Lifecycle hook that is called when a directive, pipe or service is destroyed.

Here's an example:

@Injectable()
class Service implements OnDestroy {
  ngOnDestroy() {
    console.log('Service destroy')
  }
}

@Component({
  selector: 'foo',
  template: `foo`,
  providers: [Service]
})
export class Foo implements OnDestroy {
  constructor(service: Service) {}

  ngOnDestroy() {
    console.log('foo destroy')
  }
}

@Component({
  selector: 'my-app',
  template: `<foo *ngIf="isFoo"></foo>`,
})
export class App {
  isFoo = true;

  constructor() {
    setTimeout(() => {
        this.isFoo = false;
    }, 1000)
  }
}

Notice that in the code above Service is an instance that belongs to Foo component, so it can be destroyed when Foo is destroyed.

For providers that belong to root injector this will happen on application destroy, this is helpful to avoid memory leaks with multiple bootstraps, i.e. in tests.

When a provider from parent injector is subscribed in child component, it won't be destroyed on component destroy, this is component's responsibility to unsubscribe in component ngOnDestroy (as another answer explains).

Solution 2

Create a variable in your service

subscriptions: Subscriptions[]=[];

Push each of your subscribe to the array as

this.subscriptions.push(...)

Write a dispose() method

dispose(){
this.subscriptions.forEach(subscription =>subscription.unsubscribe())

Call this method from your component during ngOnDestroy

ngOnDestroy(){
   this.service.dispose();
 }

Solution 3

I prefer this takeUntil(onDestroy$) pattern enabled by pipable operators. I like that this pattern is more concise, more clean, and it clearly conveys the intent to kill a subscription upon execution of the OnDestroy lifecycle hook.

This pattern works for services as well as components subscribing to injected observables. The skeleton code below should give you enough detail to integrate the pattern into your own service. Imagine you're importing a service called InjectedService...

import { InjectedService } from 'where/it/lives';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class MyService implements OnDestroy {

  private onDestroy$ = new Subject<boolean>();

  constructor(
    private injectedService: InjectedService
  ) {
    // Subscribe to service, and automatically unsubscribe upon `ngOnDestroy`
    this.injectedService.observableThing().pipe(
      takeUntil(this.onDestroy$)
    ).subscribe(latestTask => {
      if (latestTask) {
        this.initializeDraftAllocations();
      }
    });
  }

  ngOnDestroy() {
    this.onDestroy$.next(true);
    this.onDestroy$.complete();
  }

The topic of when/how to unsubscribe is covered extensively here: Angular/RxJs When should I unsubscribe from `Subscription`

Solution 4

Just to clarify - you don't need to destroy Observables but only the subscriptions made to them.

It seems like others have pointed out that you are now able to use ngOnDestroy with services as well. Link: https://angular.io/api/core/OnDestroy

Solution 5

Caution if using tokens

In trying to make my application as modular as possible I'll often use provider tokens to provide a service to a component. It seems that these do NOT get their ngOnDestroy methods called :-(

eg.

export const PAYMENTPANEL_SERVICE = new InjectionToken<PaymentPanelService>('PAYMENTPANEL_SERVICE');

With a provider section in a component:

 {
     provide: PAYMENTPANEL_SERVICE,
     useExisting: ShopPaymentPanelService
 }

My ShopPaymentPanelService does NOT have its ngOnDestroy method called when the component is disposed. I just found this out the hard way!

A workaround is to provide the service in conjunction with useExisting.

[
   ShopPaymentPanelService,

   {
       provide: PAYMENTPANEL_SERVICE,
       useExisting: ShopPaymentPanelService
   }
]

When I did this the ngOnDispose was called as expected.

Not sure if this is a bug or not but very unexpected.

Share:
107,893
mperle
Author by

mperle

Updated on February 11, 2022

Comments

  • mperle
    mperle over 1 year

    In an angular application we have ngOnDestroy() lifecycle hook for a component / directive and we use this hook to unsubscribe the observables.

    I want to clear / destory observable that are created in an @injectable() service. I saw some posts saying that ngOnDestroy() can be used in a service as well.

    But, is it a good practice and only way to do so and When will it get called ? someone please clarify.

  • mperle
    mperle about 6 years
    Thank you for your reply. Do we have any idea when this ngOnDestroy will be called. ?
  • mperle
    mperle about 6 years
    yes it says its a cleanup call before the directive or component gets destroyed. but i just want to understand whether is it applicable for service as well ?
  • Aravind
    Aravind about 6 years
    No services will be cleared when the module is unloaded
  • Aravind
    Aravind about 6 years
    life cycle hooks are not applicable for @injectables
  • Estus Flask
    Estus Flask about 6 years
    @Aravind I'm not sure when they were introduced but they are.
  • Aravind
    Aravind about 6 years
    @estus can you elaborate. the comment
  • Estus Flask
    Estus Flask about 6 years
    Lifecycle hooks are applicable to providers as well. Since v4, I guess.
  • Aravind
    Aravind about 6 years
    @estus I tried life cycle hooks at service it is not working in the 4.x versions too
  • Estus Flask
    Estus Flask about 6 years
    I've posted an answer that explains this.
  • Shumail
    Shumail about 6 years
    No class Service implements OnDestroy ? And what do you think when this is called if service is provided on module level
  • Estus Flask
    Estus Flask about 6 years
    implements OnDestroy doesn't affect anything but can be added for completeness. It will be called when a module is destroyed, like appModule.destroy(). This may be useful for multiple app initializations.
  • aruno
    aruno about 5 years
    @Aravind You can also just create one Subscription() object ad hoc and add to it. So you end up with _subscriptions = new Subscription(); then this._subscriptions.add(.....) and in ngOnDestroy() you just unsubscribe and don't need the array logic this._subscription.unsubscribe().
  • Aravind
    Aravind about 5 years
    Can you please elaborate more on it
  • Aravind
    Aravind about 5 years
    It's something new to me I'll check this out and see. Thanks mate
  • Ali Abbaszade
    Ali Abbaszade almost 5 years
    is unsubscribe necessary for every component that uses services?
  • Estus Flask
    Estus Flask almost 5 years
    @AliAbbaszade May depend on what it unsubscribes from. But likely yes, otherwise this will result in memory leaks.
  • Andrei Sinitson
    Andrei Sinitson about 4 years
    Great hint! I was wondering why it wasn't working in my case (I was using abstract class-interface as a token for concrete implementation).
  • user2900572
    user2900572 about 4 years
    A lifecycle hook that is called when a directive, pipe, or service is destroyed. Use for any custom cleanup that needs to occur when the instance is destroyed.
  • compuguru
    compuguru almost 4 years
    The Plunker wasn't working for me, so here's a StackBlitz version of the example: stackblitz.com/edit/angular-mggk9b
  • Tim
    Tim almost 4 years
    I think you are missing implements OnDestroy in the declaration of your component (Foo)
  • Jasmin
    Jasmin over 3 years
    I had some trouble to understand this. But this discussion helped me understand the difference between local and global services: stackoverflow.com/questions/50056446/… Whether you have to "clean up" or not depends from the scope of your service, I think.
  • Akxe
    Akxe over 2 years
    If some want to vote to fix the module not being destructed, you can do so here github.com/angular/angular/issues/37095#issuecomment-8547923‌​61
  • Eneko
    Eneko over 1 year
    I think $ prefix is usually used for observables but no for subscriptions. The convention I follow: rat is a Rat object, rats is Rat[] (or List<Rat>) and rat$ is Observable<Rat>. Anyway, IMHO this is best answer.