que patron ejemplo angular rxjs observable subscription angular-component-life-cycle

patron - que es un observable angular



Angular/RxJs ¿Cuándo debo darme de baja de `Subscription` (18)

--- Edición 4 - Recursos adicionales (2018/09/01)

En un episodio reciente de Adventures in Angular, Ben Lesh y Ward Bell discuten los problemas en torno a cómo / cuándo darse de baja en un componente. La discusión comienza aproximadamente a las 1:05:30.

Ward menciona que right now there''s an awful takeUntil dance that takes a lot of machinery y Shai Reznik menciona que Angular handles some of the subscriptions like http and routing .

En respuesta, Ben menciona que hay discusiones en este momento para permitir que los Observables se conecten a los eventos del ciclo de vida del componente Angular y Ward sugiere un Observable de eventos del ciclo de vida al que un componente podría suscribirse como una forma de saber cuándo completar los Observables mantenidos como estado interno del componente.

Dicho esto, en su mayoría necesitamos soluciones ahora, así que aquí hay algunos otros recursos.

  1. Una recomendación para el patrón takeUntil() del miembro del equipo central de RxJs Nicholas Jamieson y una regla tslint para ayudar a hacerla cumplir. https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

  2. Paquete npm ligero que expone un operador Observable que toma una instancia de componente ( this ) como parámetro y se da de baja automáticamente durante ngOnDestroy . https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Otra variación de lo anterior con una ergonomía ligeramente mejor si no está haciendo AOT builds (pero todos deberíamos estar haciendo AOT ahora). https://github.com/smnbbrv/ngx-rx-collector

  4. Directiva personalizada *ngSubscribe que funciona como una tubería asíncrona pero crea una vista incrustada en su plantilla para que pueda consultar el valor ''sin envolver'' en toda su plantilla. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

Menciono en un comentario al blog de Nicholas que el uso takeUntil() de takeUntil() podría ser una señal de que su componente está tratando de hacer demasiado y que se debe considerar la separación de sus componentes existentes en componentes de Presentación y Característica . Puedes entonces | async | async el Observable del componente Característica a una Input del componente Presentacional, lo que significa que no se necesitan suscripciones en ningún lado. Lea más sobre este enfoque here

--- Edición 3 - La solución ''oficial'' (2017/04/09)

Hablé con Ward Bell sobre esta pregunta en NGConf (incluso le mostré esta respuesta y dijo que era correcta), pero me dijo que el equipo de documentación de Angular tenía una solución para esta pregunta que no se ha publicado (aunque están trabajando para que sea aprobada). ) También me dijo que podía actualizar mi respuesta SO con la próxima recomendación oficial.

La solución que todos deberíamos usar en el futuro es agregar un private ngUnsubscribe = new Subject(); campo a todos los componentes que tienen llamadas .subscribe() a Observable s dentro de su código de clase.

Luego llamamos this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); en nuestros métodos ngOnDestroy() .

La salsa secreta (como ya señaló @metamaker ) es llamar a takeUntil(this.ngUnsubscribe) antes de cada una de nuestras llamadas .subscribe() que garantizará que todas las suscripciones se limpien cuando se destruya el componente.

Ejemplo:

import { Component, OnDestroy, OnInit } from ''@angular/core''; // RxJs 6.x+ import paths import { filter, startWith, takeUntil } from ''rxjs/operators''; import { Subject } from ''rxjs''; import { BookService } from ''../books.service''; @Component({ selector: ''app-books'', templateUrl: ''./books.component.html'' }) export class BooksComponent implements OnDestroy, OnInit { private ngUnsubscribe = new Subject(); constructor(private booksService: BookService) { } ngOnInit() { this.booksService.getBooks() .pipe( startWith([]), filter(books => books.length > 0), takeUntil(this.ngUnsubscribe) ) .subscribe(books => console.log(books)); this.booksService.getArchivedBooks() .pipe(takeUntil(this.ngUnsubscribe)) .subscribe(archivedBooks => console.log(archivedBooks)); } ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); } }

Nota: Es importante agregar el operador takeUntil como el último para evitar fugas con observables intermedios en la cadena del operador.

--- Editar 2 (28/12/2016)

Fuente 5

El tutorial Angular, el capítulo Enrutamiento ahora establece lo siguiente: "El enrutador administra los observables que proporciona y localiza las suscripciones. Las suscripciones se limpian cuando se destruye el componente, protegiéndolo contra pérdidas de memoria, por lo que no necesitamos cancelar la suscripción de los parámetros de ruta observables ". - Mark Rajcok

Aquí hay una discussion sobre los problemas de Github para los documentos angulares con respecto a los observadores de enrutadores, donde Ward Bell menciona que se está aclarando todo esto.

--- Editar 1

Fuente 4

En este video de NgEurope, Rob Wormald también dice que no es necesario darse de baja de Router Observables. También menciona el servicio http y ActivatedRoute.params en este video de noviembre de 2016 .

--- Respuesta original

TLDR:

Para esta pregunta hay (2) tipos de Observables : valor finito e valor infinito .

http Observables http producen valores finitos (1) y algo así como un event listener DOM Los Observables producen valores infinitos .

Si llama manualmente a subscribe (sin usar tubería asíncrona), unsubscribe de Observables infinitos .

No se preocupe por los finitos , los RxJs se encargarán de ellos.

Fuente 1

Rastreé una respuesta de Rob Wormald en Angular''s Gitter here .

Él afirma (me reorganicé para mayor claridad y el énfasis es mío)

Si es una secuencia de un solo valor (como una solicitud http), la limpieza manual es innecesaria (suponiendo que se suscriba manualmente en el controlador)

debería decir "si es una secuencia que se completa " (de las cuales las secuencias de valor único, a la http, son una)

si es una secuencia infinita , debe darse de baja, lo que la tubería asíncrona hace por usted

También menciona en este video de YouTube en Observables que they clean up after themselves ... en el contexto de los Observables que se complete (como Promesas, que siempre se completan porque siempre están produciendo 1 valor y terminando), nunca nos preocupamos por cancelar la suscripción de Promesas a asegúrese de que limpien los oyentes de eventos xhr , ¿verdad?).

Fuente 2

También en la guía Rangle de Angular 2 se lee

En la mayoría de los casos, no necesitaremos llamar explícitamente al método de cancelación de la suscripción a menos que queramos cancelar antes o que nuestro Observable tenga una vida útil más larga que nuestra suscripción. El comportamiento predeterminado de los operadores Observables es deshacerse de la suscripción tan pronto como se publiquen los mensajes .complete () o .error (). Tenga en cuenta que RxJS fue diseñado para ser utilizado en una forma de "disparar y olvidar" la mayor parte del tiempo.

¿Cuándo se aplica la frase our Observable has a longer lifespan than our subscription ?

Se aplica cuando se crea una suscripción dentro de un componente que se destruye antes (o no ''mucho'' antes) de que el Observable complete.

Leí esto como significado si nos suscribimos a una solicitud http o un observable que emite 10 valores y nuestro componente se destruye antes de que esa solicitud http regrese o se hayan emitido los 10 valores, ¡todavía estamos bien!

Cuando la solicitud regrese o finalmente se emita el décimo valor, el Observable se completará y se limpiarán todos los recursos.

Fuente 3

Si miramos este ejemplo de la misma guía Rangle, podemos ver que la Subscription a route.params requiere una unsubscribe() porque no sabemos cuándo dejarán de cambiar esos params (emitiendo nuevos valores).

El componente podría destruirse navegando, en cuyo caso los parámetros de ruta probablemente seguirán cambiando (técnicamente podrían cambiar hasta que finalice la aplicación) y los recursos asignados en la suscripción aún se asignarían porque no se ha completion .

¿Cuándo debo almacenar las instancias de Subscription e invocar unsubscribe() durante el ciclo de vida de NgOnDestroy y cuándo puedo simplemente ignorarlas?

Guardar todas las suscripciones introduce mucho desorden en el código del componente.

La Guía del cliente HTTP ignora las suscripciones como esta:

getHeroes() { this.heroService.getHeroes() .subscribe( heroes => this.heroes = heroes, error => this.errorMessage = <any>error); }

Al mismo tiempo, la Guía de ruta y navegación dice que:

Finalmente, navegaremos a otro lugar. El enrutador eliminará este componente del DOM y lo destruirá. Necesitamos limpiar después de nosotros mismos antes de que eso suceda. Específicamente, debemos cancelar la suscripción antes de que Angular destruya el componente. De lo contrario, podría crear una pérdida de memoria.

Nos damos de baja de nuestro Observable en el método ngOnDestroy .

private sub: any; ngOnInit() { this.sub = this.route.params.subscribe(params => { let id = +params[''id'']; // (+) converts string ''id'' to a number this.service.getHero(id).then(hero => this.hero = hero); }); } ngOnDestroy() { this.sub.unsubscribe(); }


En caso de que sea necesario darse de baja, se puede utilizar el siguiente operador para el método de tubería observable

import { Observable, Subject } from ''rxjs''; import { takeUntil } from ''rxjs/operators''; import { OnDestroy } from ''@angular/core''; export const takeUntilDestroyed = (componentInstance: OnDestroy) => <T>(observable: Observable<T>) => { const subjectPropertyName = ''__takeUntilDestroySubject__''; const originalOnDestroy = componentInstance.ngOnDestroy; const componentSubject = componentInstance[subjectPropertyName] as Subject<any> || new Subject(); componentInstance.ngOnDestroy = (...args) => { originalOnDestroy.apply(componentInstance, args); componentSubject.next(true); componentSubject.complete(); }; return observable.pipe(takeUntil<T>(componentSubject)); };

se puede usar así:

import { Component, OnDestroy, OnInit } from ''@angular/core''; import { Observable } from ''rxjs''; @Component({ template: ''<div></div>'' }) export class SomeComponent implements OnInit, OnDestroy { ngOnInit(): void { const observable = Observable.create(observer => { observer.next(''Hello''); }); observable .pipe(takeUntilDestroyed(this)) .subscribe(val => console.log(val)); } ngOnDestroy(): void { } }

El operador ajusta el método ngOnDestroy del componente.

Importante: el operador debe ser el último en tubería observable.


Me gustan las dos últimas respuestas, pero tuve un problema si el la subclase referencia "this" en ngOnDestroy .

Lo modifiqué para que sea esto, y parece que resolvió ese problema.

export abstract class BaseComponent implements OnDestroy { protected componentDestroyed$: Subject<boolean>; constructor() { this.componentDestroyed$ = new Subject<boolean>(); let f = this.ngOnDestroy; this.ngOnDestroy = function() { // without this I was getting an error if the subclass had // this.blah() in ngOnDestroy f.bind(this)(); this.componentDestroyed$.next(true); this.componentDestroyed$.complete(); }; } /// placeholder of ngOnDestroy. no need to do super() call of extended class. ngOnDestroy() {} }


Otra pequeña adición a las situaciones mencionadas anteriormente es:

  • Siempre anule la suscripción, cuando los nuevos valores en la secuencia suscrita ya no sean necesarios o no importen, resultará en una menor cantidad de disparadores y aumentará el rendimiento en algunos casos. Casos como componentes donde los datos / eventos suscritos ya no existen o se requiere una nueva suscripción a una transmisión completamente nueva (actualización, etc.) es un buen ejemplo para cancelar la suscripción.

Para manejar la suscripción, uso una clase de "Cancelar suscripción".

Aquí está la clase para darse de baja.

export class Unsubscriber implements OnDestroy { private subscriptions: Subscription[] = []; addSubscription(subscription: Subscription | Subscription[]) { if (Array.isArray(subscription)) { this.subscriptions.push(...subscription); } else { this.subscriptions.push(subscription); } } unsubscribe() { this.subscriptions .filter(subscription => subscription) .forEach(subscription => { subscription.unsubscribe(); }); } ngOnDestroy() { this.unsubscribe(); } }

Y puede usar esta clase en cualquier componente / Servicio / Efecto, etc.

Ejemplo:

class SampleComponent extends Unsubscriber { constructor () { super(); } this.addSubscription(subscription); }


Por lo general, debe darse de baja cuando los componentes se destruyen, pero Angular lo manejará cada vez más a medida que avanzamos, por ejemplo, en una nueva versión menor de Angular4, tienen esta sección para cancelar la suscripción de enrutamiento:

¿Necesitas darte de baja?

Como se describe en la sección ActivatedRoute: la ventanilla única para la información de ruta de la página Enrutamiento y navegación, el enrutador administra los observables que proporciona y localiza las suscripciones. Las suscripciones se limpian cuando se destruye el componente, lo que protege contra pérdidas de memoria, por lo que no necesita darse de baja de la ruta paramMap Observable.

Además, el ejemplo a continuación es un buen ejemplo de Angular para crear un componente y destruirlo después, vea cómo el componente implementa OnDestroy, si necesita onInit, también puede implementarlo en su componente, como implementos OnInit, OnDestroy

import { Component, Input, OnDestroy } from ''@angular/core''; import { MissionService } from ''./mission.service''; import { Subscription } from ''rxjs/Subscription''; @Component({ selector: ''my-astronaut'', template: ` <p> {{astronaut}}: <strong>{{mission}}</strong> <button (click)="confirm()" [disabled]="!announced || confirmed"> Confirm </button> </p> ` }) export class AstronautComponent implements OnDestroy { @Input() astronaut: string; mission = ''<no mission announced>''; confirmed = false; announced = false; subscription: Subscription; constructor(private missionService: MissionService) { this.subscription = missionService.missionAnnounced$.subscribe( mission => { this.mission = mission; this.announced = true; this.confirmed = false; }); } confirm() { this.confirmed = true; this.missionService.confirmMission(this.astronaut); } ngOnDestroy() { // prevent memory leak when component destroyed this.subscription.unsubscribe(); } }


Probé la solución de seangwright (Edición 3)

Eso no funciona para Observable creado por temporizador o intervalo.

Sin embargo, lo hice funcionar usando otro enfoque:

import { Component, OnDestroy, OnInit } from ''@angular/core''; import ''rxjs/add/operator/takeUntil''; import { Subject } from ''rxjs/Subject''; import { Subscription } from ''rxjs/Subscription''; import ''rxjs/Rx''; import { MyThingService } from ''../my-thing.service''; @Component({ selector: ''my-thing'', templateUrl: ''./my-thing.component.html'' }) export class MyThingComponent implements OnDestroy, OnInit { private subscriptions: Array<Subscription> = []; constructor( private myThingService: MyThingService, ) { } ngOnInit() { const newSubs = this.myThingService.getThings() .subscribe(things => console.log(things)); this.subscriptions.push(newSubs); } ngOnDestroy() { for (const subs of this.subscriptions) { subs.unsubscribe(); } } }


Puede usar la última Subscription clase para darse de baja del Observable con un código no tan desordenado.

Podemos hacer esto con, normal variable pero estará override the last subscription en cada nueva suscripción, así que evite eso, y este enfoque es muy útil cuando se trata de un mayor número de Obseravables y tipo de Obeservables como BehavoiurSubject y Subject

Suscripción

Representa un recurso desechable, como la ejecución de un Observable. Una suscripción tiene un método importante, darse de baja, que no requiere argumentos y solo elimina el recurso que posee la suscripción.

puedes usar esto de dos maneras,

  • puede enviar directamente la suscripción a la matriz de suscripción

    subscriptions:Subscription[] = []; ngOnInit(): void { this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => { //... })); this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => { //... })); } ngOnDestroy(){ // prevent memory leak when component destroyed this.subscriptions.forEach(s => s.unsubscribe()); }

  • uso add() de Subscription

    subscriptions = new Subscription(); this.subscriptions.add(subscribeOne); this.subscriptions.add(subscribeTwo); ngOnDestroy() { this.subscriptions.unsubscribe(); }

A Subscription puede retener suscripciones secundarias y cancelarlas de forma segura. Este método maneja posibles errores (por ejemplo, si alguna suscripción secundaria es nula).

Espero que esto ayude.. :)


Siguiendo la respuesta de @seangwright , he escrito una clase abstracta que maneja las suscripciones "infinitas" de observables en componentes:

import { OnDestroy } from ''@angular/core''; import { Subscription } from ''rxjs/Subscription''; import { Subject } from ''rxjs/Subject''; import { Observable } from ''rxjs/Observable''; import { PartialObserver } from ''rxjs/Observer''; export abstract class InfiniteSubscriberComponent implements OnDestroy { private onDestroySource: Subject<any> = new Subject(); constructor() {} subscribe(observable: Observable<any>): Subscription; subscribe( observable: Observable<any>, observer: PartialObserver<any> ): Subscription; subscribe( observable: Observable<any>, next?: (value: any) => void, error?: (error: any) => void, complete?: () => void ): Subscription; subscribe(observable: Observable<any>, ...subscribeArgs): Subscription { return observable .takeUntil(this.onDestroySource) .subscribe(...subscribeArgs); } ngOnDestroy() { this.onDestroySource.next(); this.onDestroySource.complete(); } }

Para usarlo, simplemente extiéndalo en su componente angular y llame al subscribe() método de la siguiente manera:

this.subscribe(someObservable, data => doSomething());

También acepta el error y completa las devoluciones de llamada como de costumbre, un objeto de observación, o no devoluciones de llamada en absoluto. Recuerde llamar super.ngOnDestroy() si también está implementando ese método en el componente secundario.

Encuentre aquí una referencia adicional de Ben Lesh: medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 .


en la aplicación SPA en la función ngOnDestroy (angular lifeCycle) Para cada suscripción , debe cancelar la suscripción . ventaja => para evitar que el estado se vuelva demasiado pesado.

por ejemplo: en el componente 1:

import {UserService} from ''./user.service''; private user = {name: ''test'', id: 1} constructor(public userService: UserService) { this.userService.onUserChange.next(this.user); }

en servicio:

import {BehaviorSubject} from ''rxjs/BehaviorSubject''; public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});

en el componente2:

import {Subscription} from ''rxjs/Subscription''; import {UserService} from ''./user.service''; private onUserChange: Subscription; constructor(public userService: UserService) { this.onUserChange = this.userService.onUserChange.subscribe(user => { console.log(user); }); } public ngOnDestroy(): void { // note: Here you have to be sure to unsubscribe to the subscribe item! this.onUserChange.unsubscribe(); }


Basado en: Uso de la herencia de clase para enganchar al ciclo de vida del componente Angular 2

Otro enfoque genérico:

export abstract class UnsubscribeOnDestroy implements OnDestroy { protected d$: Subject<any>; constructor() { this.d$ = new Subject<void>(); const f = this.ngOnDestroy; this.ngOnDestroy = () => { f(); this.d$.next(); this.d$.complete(); }; } public ngOnDestroy() { // no-op } }

Y use :

@Component({ selector: ''my-comp'', template: `` }) export class RsvpFormSaveComponent extends UnsubscribeOnDestroy implements OnInit { constructor() { super(); } ngOnInit(): void { Observable.of(''bla'') .takeUntil(this.d$) .subscribe(val => console.log(val)); } }


Dado que la solución de seangwright (Edición 3) parece ser muy útil, también encontré una molestia empaquetar esta característica en el componente base, e insinuar a otros compañeros de equipo del proyecto que recuerden llamar a super () en ngOnDestroy para activar esta función.

Esta respuesta proporciona una forma de liberarse de la súper llamada y hacer que "componentDestroyed $" sea un núcleo del componente base.

class BaseClass { protected componentDestroyed$: Subject<void> = new Subject<void>(); constructor() { /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy. let _$ = this.ngOnDestroy; this.ngOnDestroy = () => { this.componentDestroyed$.next(); this.componentDestroyed$.complete(); _$(); } } /// placeholder of ngOnDestroy. no need to do super() call of extended class. ngOnDestroy() {} }

Y luego puede usar esta función libremente, por ejemplo:

@Component({ selector: ''my-thing'', templateUrl: ''./my-thing.component.html'' }) export class MyThingComponent extends BaseClass implements OnInit, OnDestroy { constructor( private myThingService: MyThingService, ) { super(); } ngOnInit() { this.myThingService.getThings() .takeUntil(this.componentDestroyed$) .subscribe(things => console.log(things)); } /// optional. not a requirement to implement OnDestroy ngOnDestroy() { console.log(''everything works as intended with or without super call''); } }


Depende. Si al llamar a someObservable.subscribe() , comienza a retener algún recurso que debe liberarse manualmente cuando finaliza el ciclo de vida de su componente, debe llamar a theSubscription.unsubscribe() para evitar pérdidas de memoria.

Echemos un vistazo más de cerca a sus ejemplos:

getHero() devuelve el resultado de http.get() . Si observa el código fuente angular 2, http.get() crea dos oyentes de eventos:

_xhr.addEventListener(''load'', onLoad); _xhr.addEventListener(''error'', onError);

y llamando a unsubscribe() , puede cancelar la solicitud, así como a los oyentes:

_xhr.removeEventListener(''load'', onLoad); _xhr.removeEventListener(''error'', onError); _xhr.abort();

Tenga en cuenta que _xhr es específico de la plataforma, pero creo que es seguro asumir que es un XMLHttpRequest() en su caso.

Normalmente, esto es suficiente evidencia para garantizar una llamada manual unsubscribe() . Pero de acuerdo con esta especificación WHATWG , XMLHttpRequest() está sujeto a la recolección de basura una vez que está "hecho", incluso si hay oyentes de eventos adjuntos. Así que supongo que es por eso que la guía oficial angular 2 omite unsubscribe() y permite que GC limpie a los oyentes.

En cuanto a su segundo ejemplo, depende de la implementación de los params . A partir de hoy, la guía oficial angular ya no muestra la cancelación de la suscripción de params . Miré en src nuevamente y descubrí que params es solo un BehaviorSubject . Como no se utilizaron detectores de eventos ni temporizadores, y no se crearon variables globales, debería ser seguro omitir unsubscribe() .

La conclusión de su pregunta es que siempre llame a unsubscribe() como protección contra la pérdida de memoria, a menos que esté seguro de que la ejecución del observable no crea variables globales, agrega oyentes de eventos, establece temporizadores o hace cualquier otra cosa que resulte en fugas de memoria.

En caso de duda, investigue la implementación de ese observable. Si el observable ha escrito alguna lógica de limpieza en su unsubscribe() , que generalmente es la función que devuelve el constructor, entonces tiene buenas razones para considerar seriamente llamar a unsubscribe() .


La clase de suscripción tiene una característica interesante:

Representa un recurso desechable, como la ejecución de un Observable. Una suscripción tiene un método importante, darse de baja, que no requiere argumentos y solo elimina el recurso que posee la suscripción.
Además, las suscripciones pueden agruparse mediante el método add (), que adjuntará una Suscripción secundaria a la Suscripción actual. Cuando una suscripción se cancela, todos sus hijos (y sus nietos) también se cancelarán.

Puede crear un objeto de Suscripción agregado que agrupe todas sus suscripciones. Para ello, cree una suscripción vacía y agregue suscripciones a ella utilizando su método add() . Cuando su componente se destruye, solo necesita cancelar la suscripción agregada.

@Component({ ... }) export class SmartComponent implements OnInit, OnDestroy { private subscriptions = new Subscription(); constructor(private heroService: HeroService) { } ngOnInit() { this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes)); this.subscriptions.add(/* another subscription */); this.subscriptions.add(/* and another subscription */); this.subscriptions.add(/* and so on */); } ngOnDestroy() { this.subscriptions.unsubscribe(); } }


La documentación oficial de Angular 2 proporciona una explicación de cuándo darse de baja y cuándo puede ignorarse de manera segura. Echa un vistazo a este enlace:

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Busque el párrafo con el título Los padres y los niños se comunican a través de un servicio y luego el cuadro azul:

Tenga en cuenta que capturamos la suscripción y cancelamos la suscripción cuando se destruye el Componente Astronauta. Este es un paso de protección de pérdida de memoria. No hay ningún riesgo real en esta aplicación porque la vida útil de un componente de astronauta es la misma que la vida útil de la aplicación en sí. Eso no siempre sería cierto en una aplicación más compleja.

No agregamos este protector al Componente de control de misión porque, como padre, controla la vida útil del Servicio de misión.

Espero que esto te ayude.


La respuesta oficial de Edit # 3 (y variaciones) funciona bien, pero lo que me sorprende es el "enturbiamiento" de la lógica de negocios en torno a la suscripción observable.

Aquí hay otro enfoque usando envoltorios.

Warining: código experimental

El archivo subscribeAndGuard.ts se usa para crear una nueva extensión Observable para envolver .subscribe() y dentro de ella para envolver ngOnDestroy() .
El uso es el mismo que .subscribe() , excepto por un primer parámetro adicional que hace referencia al componente.

import { Observable } from ''rxjs/Observable''; import { Subscription } from ''rxjs/Subscription''; const subscribeAndGuard = function(component, fnData, fnError = null, fnComplete = null) { // Define the subscription const sub: Subscription = this.subscribe(fnData, fnError, fnComplete); // Wrap component''s onDestroy if (!component.ngOnDestroy) { throw new Error(''To use subscribeAndGuard, the component must implement ngOnDestroy''); } const saved_OnDestroy = component.ngOnDestroy; component.ngOnDestroy = () => { console.log(''subscribeAndGuard.onDestroy''); sub.unsubscribe(); // Note: need to put original back in place // otherwise ''this'' is undefined in component.ngOnDestroy component.ngOnDestroy = saved_OnDestroy; component.ngOnDestroy(); }; return sub; }; // Create an Observable extension Observable.prototype.subscribeAndGuard = subscribeAndGuard; // Ref: https://www.typescriptlang.org/docs/handbook/declaration-merging.html declare module ''rxjs/Observable'' { interface Observable<T> { subscribeAndGuard: typeof subscribeAndGuard; } }

Aquí hay un componente con dos suscripciones, una con el contenedor y otra sin él. La única advertencia es que debe implementar OnDestroy (con el cuerpo vacío si lo desea), de lo contrario, Angular no sabe llamar a la versión envuelta.

import { Component, OnInit, OnDestroy } from ''@angular/core''; import { Observable } from ''rxjs/Observable''; import ''rxjs/Rx''; import ''./subscribeAndGuard''; @Component({ selector: ''app-subscribing'', template: ''<h3>Subscribing component is active</h3>'', }) export class SubscribingComponent implements OnInit, OnDestroy { ngOnInit() { // This subscription will be terminated after onDestroy Observable.interval(1000) .subscribeAndGuard(this, (data) => { console.log(''Guarded:'', data); }, (error) => { }, (/*completed*/) => { } ); // This subscription will continue after onDestroy Observable.interval(1000) .subscribe( (data) => { console.log(''Unguarded:'', data); }, (error) => { }, (/*completed*/) => { } ); } ngOnDestroy() { console.log(''SubscribingComponent.OnDestroy''); } }

Un demo plunker está here

Una nota adicional: Re Edit 3 - La solución ''oficial'', esto se puede simplificar usando takeWhile () en lugar de takeUntil () antes de las suscripciones, y un simple booleano en lugar de otro Observable en ngOnDestroy.

@Component({...}) export class SubscribingComponent implements OnInit, OnDestroy { iAmAlive = true; ngOnInit() { Observable.interval(1000) .takeWhile(() => { return this.iAmAlive; }) .subscribe((data) => { console.log(data); }); } ngOnDestroy() { this.iAmAlive = false; } }


No necesita tener muchas suscripciones y darse de baja manualmente. Use el combo Asunto y takeUntil para manejar suscripciones como un jefe:

import {Subject} from "rxjs/Subject"; @Component( { moduleId: __moduleName, selector: ''my-view'', templateUrl: ''../views/view-route.view.html'', } ) export class ViewRouteComponent implements OnDestroy { componentDestroyed$: Subject<boolean> = new Subject(); constructor(protected titleService: TitleService) { this.titleService.emitter1$ .takeUntil(this.componentDestroyed$) .subscribe( (data: any) => { // ... do something 1 } ); this.titleService.emitter2$ .takeUntil(this.componentDestroyed$) .subscribe( (data: any) => { // ... do something 2 } ); // ... this.titleService.emitterN$ .takeUntil(this.componentDestroyed$) .subscribe( (data: any) => { // ... do something N } ); } ngOnDestroy() { this.componentDestroyed$.next(true); this.componentDestroyed$.complete(); } }

El enfoque alternativo , que fue propuesto por @acumartini en los comentarios , utiliza takeWhile lugar de takeUntil . Puede preferirlo, pero tenga en cuenta que de esta manera su ejecución Observable no se cancelará en ngDestroy de su componente (por ejemplo, cuando realiza cálculos que requieren mucho tiempo o espera datos del servidor). El método, que se basa en takeUntil , no tiene este inconveniente y conduce a la cancelación inmediata de la solicitud. Gracias a @AlexChe por la explicación detallada en los comentarios .

Entonces aquí está el código:

@Component( { moduleId: __moduleName, selector: ''my-view'', templateUrl: ''../views/view-route.view.html'', } ) export class ViewRouteComponent implements OnDestroy { alive: boolean = true; constructor(protected titleService: TitleService) { this.titleService.emitter1$ .takeWhile(() => this.alive) .subscribe( (data: any) => { // ... do something 1 } ); this.titleService.emitter2$ .takeWhile(() => this.alive) .subscribe( (data: any) => { // ... do something 2 } ); // ... this.titleService.emitterN$ .takeWhile(() => this.alive) .subscribe( (data: any) => { // ... do something N } ); } // Probably, this.alive = false MAY not be required here, because // if this.alive === undefined, takeWhile will stop. I // will check it as soon, as I have time. ngOnDestroy() { this.alive = false; } }


Algunas de las mejores prácticas con respecto a las bajas de observables dentro de los componentes angulares:

Una cita de Routing & Navigation

Al suscribirse a un observable en un componente, casi siempre se arregla para darse de baja cuando se destruye el componente.

Hay algunos observables excepcionales donde esto no es necesario. Los observables de ActivatedRoute se encuentran entre las excepciones.

La ruta activada y sus observables están aislados del enrutador. El enrutador destruye un componente enrutado cuando ya no es necesario y la ruta activada inyectada muere con él.

Siéntase libre de darse de baja de todos modos. Es inofensivo y nunca es una mala práctica.

Y en respuesta a los siguientes enlaces:

Recopilé algunas de las mejores prácticas con respecto a las bajas de observables dentro de los componentes de Angular para compartir con ustedes:

  • http cancelación de suscripción observable de http es condicional y deberíamos considerar los efectos de la ''devolución de llamada de suscripción'' que se ejecuta después de que el componente se destruya caso por caso. Sabemos que angular cancela la suscripción y limpia el propio http observable (1) , (2) . Si bien esto es cierto desde la perspectiva de los recursos, solo cuenta la mitad de la historia. Digamos que estamos hablando de llamar directamente a http desde un componente, y la respuesta http tardó más de lo necesario, por lo que el usuario cerró el componente. El manejador subscribe() seguirá siendo llamado incluso si el componente está cerrado y destruido. Esto puede tener efectos secundarios no deseados y, en el peor de los casos, dejar roto el estado de la aplicación. También puede causar excepciones si el código en la devolución de llamada intenta llamar a algo que acaba de eliminarse. Sin embargo, al mismo tiempo ocasionalmente se desean. Por ejemplo, supongamos que está creando un cliente de correo electrónico y activa un sonido cuando el correo electrónico termina de enviarse; bueno, aún así desearía que eso ocurra incluso si el componente está cerrado ( 8 ).
  • No es necesario darse de baja de los observables que completan o error. Sin embargo, no hay daño al hacerlo (7) .
  • Use AsyncPipe tanto como sea posible porque se da de baja automáticamente de lo observable en la destrucción de componentes.
  • route.params suscripción de los observables ActivatedRoute como route.params si están suscritos dentro de un componente anidado (agregado dentro de tpl con el selector de componentes) o dinámico, ya que pueden suscribirse muchas veces mientras exista el componente padre / host. No es necesario darse de baja de ellos en otros escenarios como se menciona en la cita anterior de los documentos de Routing & Navigation .
  • Anule la suscripción de observables globales compartidos entre componentes que están expuestos a través de un servicio Angular, por ejemplo, ya que pueden suscribirse varias veces siempre que el componente se inicialice.
  • No es necesario darse de baja de los observables internos de un servicio con ámbito de aplicación, ya que este servicio nunca se destruye, a menos que se destruya toda su aplicación, no hay ninguna razón real para darse de baja y no hay posibilidad de pérdidas de memoria. (6)

    Nota: Con respecto a los servicios con alcance, es decir, los proveedores de componentes, se destruyen cuando se destruye el componente. En este caso, si nos suscribimos a cualquier observable dentro de este proveedor, deberíamos considerar darnos de baja mediante el OnDestroy ciclo de vida de OnDestroy , que se llamará cuando se destruya el servicio, según los documentos.
  • Use una técnica abstracta para evitar cualquier desorden de código que pueda resultar de las bajas. Puede administrar sus suscripciones con takeUntil medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 o puede usar este package npm mencionado en (4) La forma más fácil de darse de baja de Observables en Angular .
  • Siempre anule la suscripción de los observables de form.valueChanges como form.valueChanges y form.statusChanges
  • Siempre anule la suscripción de los observables del servicio Renderer2 como renderer2.listen
  • Anule la suscripción de todos los demás observables como un paso de protección de pérdida de memoria hasta que Angular Docs nos diga explícitamente qué observables son innecesarios para cancelar la suscripción (Verifique el problema: (5) Documentación para la cancelación de la suscripción de RxJS (Abrir) ).
  • Bonificación: siempre use las formas angulares para vincular eventos como HostListener ya que angular se preocupa por eliminar los oyentes de eventos si es necesario y evita cualquier pérdida potencial de memoria debido a enlaces de eventos.

Un buen consejo final : si no sabe si un observable se cancela / cancela automáticamente o no, agregue una devolución de llamada complete para subscribe(...) y verifique si se llama cuando se destruye el componente.