139

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
  • 3,342
  • 8
  • 20
  • 34

8 Answers8

166

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).

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • No `class Service implements OnDestroy` ? And what do you think when this is called if service is provided on module level – Shumail Sep 04 '17 at 21:48
  • 1
    `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. – Estus Flask Sep 04 '17 at 22:05
  • 1
    is unsubscribe necessary for every component that uses services? – Ali Abbaszade Nov 13 '18 at 07:16
  • @AliAbbaszade May depend on what it unsubscribes from. But likely yes, otherwise this will result in memory leaks. – Estus Flask Nov 13 '18 at 07:20
  • 3
    The Plunker wasn't working for me, so here's a StackBlitz version of the example: https://stackblitz.com/edit/angular-mggk9b – compuguru Dec 23 '19 at 22:15
  • I think you are missing implements OnDestroy in the declaration of your component (Foo) – Tim Jan 30 '20 at 00:31
  • 1
    I had some trouble to understand this. But this discussion helped me understand the difference between local and global services: https://stackoverflow.com/questions/50056446/what-is-the-lifecycle-of-a-service-in-angular-5 Whether you have to "clean up" or not depends from the scope of your service, I think. – Jasmin May 15 '20 at 08:22
  • If some want to vote to fix the module not being destructed, you can do so here https://github.com/angular/angular/issues/37095#issuecomment-854792361 – Akxe Jun 04 '21 at 15:06
32

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();
 }
Aravind
  • 40,391
  • 16
  • 91
  • 110
  • Thank you for your reply. Do we have any idea when this ngOnDestroy will be called. ? – mperle Aug 26 '17 at 19:34
  • 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 ? – mperle Aug 26 '17 at 19:42
  • No services will be cleared when the module is unloaded – Aravind Aug 26 '17 at 19:44
  • 2
    life cycle hooks are not applicable for `@injectables` – Aravind Aug 26 '17 at 19:52
  • @Aravind I'm not sure when they were introduced but [they are](https://github.com/angular/angular/blob/4.3.5/packages/core/src/view/provider.ts#L506-L527). – Estus Flask Aug 27 '17 at 08:31
  • @estus can you elaborate. the comment – Aravind Aug 27 '17 at 08:37
  • Lifecycle hooks are applicable to providers as well. Since v4, I guess. – Estus Flask Aug 27 '17 at 08:47
  • @estus I tried life cycle hooks at service it is not working in the 4.x versions too – Aravind Aug 27 '17 at 10:04
  • I've posted an answer that explains this. – Estus Flask Aug 27 '17 at 10:31
  • @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()`. – Simon_Weaver Aug 14 '18 at 23:24
  • It's something new to me I'll check this out and see. Thanks mate – Aravind Aug 15 '18 at 07:55
  • 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. – user2900572 Oct 30 '19 at 09:22
18

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`

Matthew Marichiba
  • 1,942
  • 1
  • 23
  • 24
5

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

apeshev
  • 197
  • 1
  • 6
4

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.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • 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). – Andrei Sinitson Sep 27 '19 at 11:13
1

Create a variable in your service:

private subscriptions$ = new Subscription();

Add each of your subscriptions to observable in constructor (or in ngOnInit lifecycle hook)

ngOnInit() {
  this.subscriptions.add(...)
  this.subscriptions.add(...)
}

Call this method from your component on destroy to unsubscribe from all subscriptions and child subscriptions.

ngOnDestroy(){
   this.subsriptions$.unsubscribe();
}
  • 1
    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) and rat$ is Observable. Anyway, IMHO this is best answer. – Eneko Feb 10 '22 at 11:53
0

I recommend using .pipe(take(1)).subscribe(). To avoid setting up an ongoing subscription.

0

TLDR;

import {DestroyRef} from "@angular/core";

// ...

constructor(destroyRef: DestroyRef) {
  fromEvent(window, 'resize')
    .pipe(takeUntilDestroyed(destroyRef))
    .subscribe(console.warn);
}

The Explanation

Inject the DestroyRef into your target stereotype (module, service, directive, component, pipe), then use RxJS takeUntilDestroyed operator.

This is because Angular's dependency injection (DI) & inversion of control (IoC) are hierarchal. Thus, each stereotype has its own injector, usually inheriting from a parent injector.

And so, each injector manager its own lifecycle and exposes a DestroyRef.

Salathiel Genese
  • 1,639
  • 2
  • 21
  • 37