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.

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


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

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`



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.


Ó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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.