Angular 4+ ngOnDestroy () en servicio - destruir observable

103

En una aplicación angular, tenemos un ngOnDestroy()gancho de ciclo de vida para un componente / directiva y usamos este gancho para cancelar la suscripción de los observables.

Quiero claros / destructores observables que se crean en un @injectable()servicio. Vi algunas publicaciones que decían que también ngOnDestroy()se puede usar en un servicio.

Pero, ¿es una buena práctica y la única forma de hacerlo y cuándo se llamará? alguien por favor aclare.

mperle
fuente

Respuestas:

119

El enlace del ciclo de vida de OnDestroy está disponible en los proveedores. Según los documentos:

Enlace de ciclo de vida que se llama cuando se destruye una directiva, canalización o servicio.

He aquí un ejemplo :

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

Tenga en cuenta que en el código anterior Servicehay una instancia que pertenece al Foocomponente, por lo que puede destruirse cuando Foose destruye.

Para los proveedores que pertenecen a root injector, esto sucederá al destruir la aplicación, esto es útil para evitar pérdidas de memoria con múltiples bootstraps, es decir, en pruebas.

Cuando un proveedor del inyector principal está suscrito en el componente secundario, no se destruirá al destruir el componente, es responsabilidad del componente cancelar la suscripción en el componente ngOnDestroy(como explica otra respuesta).

Matraz Estus
fuente
No class Service implements OnDestroy? ¿Y qué piensas cuando se llama a esto si el servicio se proporciona a nivel de módulo
Shumail
1
implements OnDestroyno afecta nada pero se puede agregar para completar. Se llamará cuando se destruya un módulo, como appModule.destroy(). Esto puede resultar útil para varias inicializaciones de aplicaciones.
Estus Flask
1
¿Es necesario cancelar la suscripción para todos los componentes que utilizan servicios?
Ali Abbaszade
2
El Plunker no estaba funcionando para mí, así que aquí hay una versión de StackBlitz del ejemplo: stackblitz.com/edit/angular-mggk9b
compuguru
1
Tuve algunos problemas para entender esto. Pero esta discusión me ayudó a comprender la diferencia entre los servicios locales y globales: stackoverflow.com/questions/50056446/… Creo que si tienes que "limpiar" o no depende del alcance de tu servicio.
Jasmin
25

Crea una variable en tu servicio

subscriptions: Subscriptions[]=[];

Empuje cada uno de sus suscriptores a la matriz como

this.subscriptions.push(...)

Escribe un dispose()método

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

Llame a este método desde su componente durante ngOnDestroy

ngOnDestroy(){
   this.service.dispose();
 }
Aravind
fuente
Gracias por su respuesta. ¿Tenemos alguna idea de cuándo se llamará a este ngOnDestroy? ?
mperle
sí, dice que es una llamada de limpieza antes de que se destruya la directiva o el componente. pero solo quiero saber si también es aplicable para el servicio.
mperle
No se borrarán los servicios cuando se descargue el módulo
Aravind
2
los ganchos del ciclo de vida no son aplicables para@injectables
Aravind
@Aravind No estoy seguro de cuándo se presentaron, pero lo están .
Estus Flask
11

Prefiero este takeUntil(onDestroy$)patrón habilitado por operadores pipables. Me gusta que este patrón sea más conciso, más limpio y transmita claramente la intención de eliminar una suscripción tras la ejecución del OnDestroyenlace del ciclo de vida.

Este patrón funciona tanto para servicios como para componentes que se suscriben a observables inyectados. El código esqueleto a continuación debería brindarle suficientes detalles para integrar el patrón en su propio servicio. Imagina que estás importando un servicio llamado 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();
  }

El tema de cuándo / cómo cancelar la suscripción se trata ampliamente aquí: Angular / RxJs ¿Cuándo debo cancelar la suscripción de la suscripción?

Matthew Marichiba
fuente
5

Solo para aclarar: no es necesario destruir, Observablessolo las suscripciones realizadas a ellos.

Parece que otros han señalado que ahora también puede usar ngOnDestroycon servicios. Enlace: https://angular.io/api/core/OnDestroy

apeshev
fuente
1
¿Puede dar más detalles al respecto
Aravind
2

Precaución si usa tokens

Al intentar hacer que mi aplicación sea lo más modular posible, a menudo utilizo tokens de proveedor para proporcionar un servicio a un componente. Parece que estos NO obtienen sus ngOnDestroymétodos llamados :-(

p.ej.

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

Con una sección de proveedor en un componente:

 {
     provide: PAYMENTPANEL_SERVICE,
     useExisting: ShopPaymentPanelService
 }

My ShopPaymentPanelServiceNO tiene su ngOnDestroymétodo llamado cuando se elimina el componente. ¡Acabo de descubrir esto por las malas!

Una solución alternativa es proporcionar el servicio junto con useExisting.

[
   ShopPaymentPanelService,

   {
       provide: PAYMENTPANEL_SERVICE,
       useExisting: ShopPaymentPanelService
   }
]

Cuando hice esto, ngOnDisposese llamó como se esperaba.

No estoy seguro de si esto es un error o no, pero es muy inesperado.

Simon_Weaver
fuente
¡Gran pista! Me preguntaba por qué no funcionaba en mi caso (estaba usando una interfaz de clase abstracta como token para una implementación concreta).
Andrei Sinitson