Angular 4+ ngOnDestroy () em serviço - destruição observável

103

Em uma aplicação angular, temos um ngOnDestroy()gancho de ciclo de vida para um componente / diretiva e usamos esse gancho para cancelar a assinatura dos observáveis.

Quero limpar / destruir observáveis ​​que são criados em um @injectable()serviço. Eu vi alguns posts dizendo quengOnDestroy() pode ser usado em um serviço também.

Mas, é uma boa prática e a única maneira de fazer isso e quando será chamado? alguém por favor esclareça.

mperle
fonte

Respostas:

119

O gancho de ciclo de vida OnDestroy está disponível em provedores. De acordo com a documentação:

Gancho de ciclo de vida que é chamado quando uma diretiva, canal ou serviço é destruído.

Aqui está um exemplo :

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

Observe que no código acima Serviceestá uma instância que pertence ao Foocomponente, portanto, ela pode ser destruída quando Foofor destruída.

Para provedores que pertencem ao injetor de raiz, isso acontecerá na destruição do aplicativo; isso é útil para evitar vazamentos de memória com várias inicializações, ou seja, em testes.

Quando um provedor do injetor pai é inscrito no componente filho, ele não será destruído na destruição do componente, é responsabilidade do componente cancelar a inscrição no componente ngOnDestroy(como outra resposta explica).

Estus Flask
fonte
Não class Service implements OnDestroy? E o que você pensa quando isso é chamado se o serviço for fornecido no nível de módulo
Shumail
1
implements OnDestroynão afeta nada, mas pode ser adicionado para integridade. Ele será chamado quando um módulo for destruído, como appModule.destroy(). Isso pode ser útil para inicializações de vários aplicativos.
Estus Flask
1
é necessário cancelar a assinatura para cada componente que usa serviços?
Ali Abbaszade
2
O Plunker não estava funcionando para mim, então aqui está uma versão StackBlitz do exemplo: stackblitz.com/edit/angular-mggk9b
compuguru
1
Eu tive alguns problemas para entender isso. Mas essa discussão me ajudou a entender a diferença entre serviços locais e globais: stackoverflow.com/questions/50056446/… Se você tem que "limpar" ou não depende do escopo de seu serviço, eu acho.
Jasmin
25

Crie uma variável em seu serviço

subscriptions: Subscriptions[]=[];

Empurre cada um de seus inscritos para a matriz como

this.subscriptions.push(...)

Escreva um dispose()método

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

Chame este método de seu componente durante ngOnDestroy

ngOnDestroy(){
   this.service.dispose();
 }
Aravind
fonte
Obrigado por sua resposta. Temos alguma ideia de quando este ngOnDestroy será chamado. ?
mperle
sim, ele diz que é uma chamada de limpeza antes que a diretiva ou componente seja destruído. mas eu só quero entender se isso também é aplicável para serviços?
mperle
Nenhum serviço será liberado quando o módulo for descarregado
Aravind
2
ganchos de ciclo de vida não são aplicáveis ​​para@injectables
Aravind de
@Aravind Não tenho certeza de quando eles foram apresentados, mas são .
Estus Flask
11

Eu prefiro esse takeUntil(onDestroy$)padrão habilitado por operadores de tubulação. Eu gosto que esse padrão seja mais conciso, mais claro e transmita claramente a intenção de cancelar uma assinatura após a execução doOnDestroy gancho ciclo vida.

Esse padrão funciona tanto para serviços quanto para componentes que assinam observáveis ​​injetados. O esqueleto do código abaixo deve fornecer detalhes suficientes para integrar o padrão em seu próprio serviço. Imagine que você está importando um serviço chamado 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();
  }

O tópico de quando / como cancelar é coberto extensivamente aqui: Angular / RxJs Quando devo cancelar a assinatura de `Assinatura`

Matthew Marichiba
fonte
5

Só para esclarecer - você não precisa destruir, Observablesapenas as assinaturas feitas a eles.

Parece que outras pessoas indicaram que agora você também pode usar os ngOnDestroyserviços. Link: https://angular.io/api/core/OnDestroy

Apeshev
fonte
1
Você pode elaborar mais sobre isso
Aravind de
2

Cuidado ao usar tokens

Ao tentar tornar meu aplicativo o mais modular possível, geralmente usarei tokens de provedor para fornecer um serviço a um componente. Parece que estes NÃO recebem seusngOnDestroy métodos chamados :-(

por exemplo.

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

Com uma seção de provedor em um componente:

 {
     provide: PAYMENTPANEL_SERVICE,
     useExisting: ShopPaymentPanelService
 }

Meu ShopPaymentPanelServiceNÃO tem seungOnDestroy método chamado quando o componente é descartado. Eu descobri isso da maneira mais difícil!

Uma solução alternativa é fornecer o serviço em conjunto com useExisting.

[
   ShopPaymentPanelService,

   {
       provide: PAYMENTPANEL_SERVICE,
       useExisting: ShopPaymentPanelService
   }
]

Quando fiz isso, ngOnDisposefoi chamado conforme o esperado.

Não tenho certeza se isso é um bug ou não, mas muito inesperado.

Simon_Weaver
fonte
Ótima dica! Eu queria saber por que não estava funcionando no meu caso (eu estava usando a interface de classe abstrata como um token para implementação concreta).
Andrei Sinitson