mperle
mperle

Reputation: 3612

Angular 4+ ngOnDestroy() in service - destroy observable

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.

Upvotes: 150

Views: 155071

Answers (8)

Salathiel Genese
Salathiel Genese

Reputation: 1909

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.

Upvotes: 0

mobiblaize
mobiblaize

Reputation: 11

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

Upvotes: 0

Sunday Power Inemesit
Sunday Power Inemesit

Reputation: 336

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();
}

Upvotes: 2

Estus Flask
Estus Flask

Reputation: 222484

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

Upvotes: 179

Simon_Weaver
Simon_Weaver

Reputation: 145950

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.

Upvotes: 4

Matthew Marichiba
Matthew Marichiba

Reputation: 2092

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`

Upvotes: 18

apeshev
apeshev

Reputation: 197

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

Upvotes: 5

Aravind
Aravind

Reputation: 41571

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();
 }

Upvotes: 32

Related Questions