Angular 4+ ngOnDestroy () en service - détruire observable

103

Dans une application angulaire, nous avons un ngOnDestroy()hook de cycle de vie pour un composant / directive et nous utilisons ce hook pour désabonner les observables.

Je veux effacer / déstocker les observables créés dans un @injectable()service. J'ai vu des articles disant que cela ngOnDestroy()peut également être utilisé dans un service.

Mais est-ce une bonne pratique et le seul moyen de le faire et quand sera-t-il appelé? quelqu'un s'il vous plaît clarifier.

mperle
la source

Réponses:

119

Le hook de cycle de vie OnDestroy est disponible chez les fournisseurs. Selon la documentation:

Crochet de cycle de vie appelé lorsqu'une directive, un canal ou un service est détruit.

Voici un exemple :

@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)
  }
}

Notez que dans le code ci Service- dessus se trouve une instance qui appartient au Foocomposant, elle peut donc être détruite lorsqu'elle Fooest détruite.

Pour les fournisseurs qui appartiennent à l'injecteur racine, cela se produira lors de la destruction de l'application, cela est utile pour éviter les fuites de mémoire avec plusieurs bootstraps, c'est-à-dire lors des tests.

Lorsqu'un fournisseur de l'injecteur parent est abonné au composant enfant, il ne sera pas détruit lors de la destruction du composant, c'est la responsabilité du composant de se désabonner du composant ngOnDestroy(comme l'explique une autre réponse).

Fiole d'Estus
la source
Non class Service implements OnDestroy? Et que pensez-vous quand cela est appelé si le service est fourni au niveau du module
Shumail
1
implements OnDestroyn'affecte rien mais peut être ajouté par souci d'exhaustivité. Il sera appelé lorsqu'un module est détruit, comme appModule.destroy(). Cela peut être utile pour plusieurs initialisations d'applications.
Estus Flask
1
la désinscription est-elle nécessaire pour chaque composant qui utilise des services?
Ali Abbaszade
2
Le Plunker ne fonctionnait pas pour moi, alors voici une version StackBlitz de l'exemple: stackblitz.com/edit/angular-mggk9b
compuguru
1
J'ai eu du mal à comprendre cela. Mais cette discussion m'a aidé à comprendre la différence entre les services locaux et globaux: stackoverflow.com/questions/50056446/... Que vous deviez "nettoyer" ou non dépend de la portée de votre service, je pense.
Jasmin
25

Créez une variable dans votre service

subscriptions: Subscriptions[]=[];

Poussez chacun de vos abonnés à la baie comme

this.subscriptions.push(...)

Ecrire une dispose()méthode

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

Appelez cette méthode depuis votre composant pendant ngOnDestroy

ngOnDestroy(){
   this.service.dispose();
 }
Aravind
la source
Merci pour votre réponse. Avons-nous une idée quand ce ngOnDestroy sera appelé. ?
mperle
oui, il dit que c'est un appel de nettoyage avant que la directive ou le composant ne soit détruit. mais je veux juste savoir si cela s'applique également au service?
mperle
Aucun service ne sera effacé lorsque le module sera déchargé
Aravind
2
les crochets de cycle de vie ne sont pas applicables pour@injectables
Aravind
@Aravind Je ne sais pas quand ils ont été introduits mais ils le sont .
Estus Flask
11

Je préfère ce takeUntil(onDestroy$)modèle activé par les opérateurs pipables. J'aime le fait que ce modèle soit plus concis, plus propre et qu'il exprime clairement l'intention de tuer un abonnement lors de l'exécution du OnDestroyhook de cycle de vie.

Ce modèle fonctionne pour les services ainsi que les composants souscrivant à des observables injectés. Le code squelette ci-dessous devrait vous donner suffisamment de détails pour intégrer le modèle dans votre propre service. Imaginez que vous importez un service appelé 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();
  }

Le sujet de quand / comment se désabonner est couvert en détail ici: Angular / RxJs Quand dois-je me désinscrire de `Subscription`

Matthieu Marichiba
la source
5

Juste pour clarifier - vous n'avez pas besoin de détruire Observablesmais seulement les abonnements qui leur sont faits.

Il semble que d'autres aient souligné que vous pouvez désormais également utiliser ngOnDestroydes services. Lien: https://angular.io/api/core/OnDestroy

apeshev
la source
1
Pouvez-vous s'il vous plaît élaborer plus à ce sujet
Aravind
2

Attention si vous utilisez des jetons

En essayant de rendre mon application aussi modulaire que possible, j'utiliserai souvent des jetons de fournisseur pour fournir un service à un composant. Il semble que ceux-ci n'obtiennent PAS leurs ngOnDestroyméthodes appelées :-(

par exemple.

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

Avec une section fournisseur dans un composant:

 {
     provide: PAYMENTPANEL_SERVICE,
     useExisting: ShopPaymentPanelService
 }

My ShopPaymentPanelServicen'a PAS sa ngOnDestroyméthode appelée lorsque le composant est supprimé. Je viens de découvrir cela à la dure!

Une solution de contournement consiste à fournir le service conjointement avec useExisting.

[
   ShopPaymentPanelService,

   {
       provide: PAYMENTPANEL_SERVICE,
       useExisting: ShopPaymentPanelService
   }
]

Quand j'ai fait cela, le a ngOnDisposeété appelé comme prévu.

Je ne sais pas si c'est un bug ou pas mais très inattendu.

Simon_Weaver
la source
Bon indice! Je me demandais pourquoi cela ne fonctionnait pas dans mon cas (j'utilisais une interface de classe abstraite comme un jeton pour une implémentation concrète).
Andrei Sinitson le