¿Cuándo debo almacenar las Subscription
instancias 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 elngOnDestroy
método.
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();
}
angular
rxjs
observable
subscription
angular-component-life-cycle
Sergey Tihon
fuente
fuente
Subscription
quehttp-requests
se puede ignorar, ya que solo llamanonNext
una vez y luego llamanonComplete
. En suRouter
lugar, llamaonNext
repetidamente y puede que nunca llameonComplete
(no estoy seguro de eso ...). Lo mismo ocurre conObservable
s deEvent
s. Así que supongo que deberían serunsubscribed
.subscribe
sí mismo la que potencialmente asigna recursos aguas arriba.Respuestas:
--- 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
right now there's an awful takeUntil dance that takes a lot of machinery
y Shai Reznik mencionaAngular 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 los eventos del ciclo de vida al que un componente podría suscribirse para 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.
Una recomendación para el
takeUntil()
patrón del miembro del equipo central de RxJ, Nicholas Jamieson, y una regla tslint para ayudar a hacerla cumplir. https://ncjamieson.com/avoiding-takeuntil-leaks/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 durantengOnDestroy
. https://github.com/NetanelBasal/ngx-take-until-destroyOtra 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
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-c8f6e762697fMenciono en un comentario al blog de Nicholas que el uso excesivo 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 . Luego puede hacer el Observable desde el componente Característica en uno del componente Presentacional, lo que significa que no se necesitan suscripciones en ningún lado. Lea más sobre este enfoque aquí| async
Input
--- 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 lograr 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.subscribe()
llamadas aObservable
s dentro de su código de clase.Luego llamamos
this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
a nuestrosngOnDestroy()
métodos.La salsa secreta (como ya señaló @metamaker ) es llamar
takeUntil(this.ngUnsubscribe)
antes de cada una de nuestras.subscribe()
llamadas, lo que garantizará que todas las suscripciones se limpien cuando se destruya el componente.Ejemplo:
Nota: Es importante agregar el
takeUntil
operador como el último para evitar fugas con observables intermedios en la cadena del operador.--- Edición 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 discusión sobre los problemas de Github para los documentos angulares con respecto a los observadores del enrutador, 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
http
servicio yActivatedRoute.params
en este video de noviembre de 2016 .--- Respuesta original
TLDR:
Para esta pregunta hay (2) tipos de
Observables
- valor finito y valor infinito .http
Observables
producir valores finitos (1) y algo así como un DOMevent listener
Observables
produce valores infinitos .Si llama manualmente
subscribe
(sin usar tubería asíncrona), entoncesunsubscribe
desde infinitoObservables
.No te preocupes por los finitos , los cuidaremos
RxJs
.Fuente 1
Rastreé una respuesta de Rob Wormald en Angular's Gitter aquí .
Él afirma (me reorganicé para mayor claridad y el énfasis es mío)
También menciona en este video de YouTube en Observables que
they clean up after themselves
... en el contexto de Observables quecomplete
(como Promesas, que siempre se completan porque siempre producen 1 valor y finalizan), nunca nos preocupamos por cancelar la suscripción a Promesas para asegurarnos de que limpien elxhr
evento oyentes, ¿verdad?Fuente 2
También en la guía Rangle de Angular 2 se lee
¿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 se
Observable
complete.Leí esto como significado si nos suscribimos a una
http
solicitud o un observable que emite 10 valores y nuestro componente se destruye antes de que esahttp
solicitud regrese o se hayan emitido los 10 valores, ¡todavía estamos bien!Cuando la solicitud regrese o finalmente se emita el décimo valor,
Observable
se completará y todos los recursos se limpiarán.Fuente 3
Si miramos este ejemplo de la misma guía Rangle, podemos ver que el
Subscription
toroute.params
requiere ununsubscribe()
porque no sabemos cuándoparams
dejarán de cambiar (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 ha habido un
completion
.fuente
complete()
por sí solo no parece limpiar las suscripciones. Sin embargo, al llamarnext()
y luego locomplete()
hace, creo quetakeUntil()
solo se detiene cuando se produce un valor, no cuando finaliza la secuencia.Subject
dentro de un componente y que se activangIf
para activarngOnInit
yngOnDestroy
muestra que el sujeto y sus suscripciones nunca se completarán o eliminarán (conectó unfinally
operador a la suscripción). Debo llamarSubject.complete()
enngOnDestroy
, por lo que las suscripciones pueden limpiar ellos mismos.takeUnitl
utilizamos el enfoque, ¿nunca tendremos que darnos de baja manualmente de ningún observable? ¿Es ese el caso? Por otra parte, ¿por qué necesitamos a la llamadanext()
en elngOnDestroy
, ¿por qué no llamamoscomplete()
?No necesita tener muchas suscripciones y darse de baja manualmente. Use el combo Asunto y takeUntil para manejar suscripciones como un jefe:
El enfoque alternativo , que fue propuesto por @acumartini en los comentarios , utiliza takeWhile en 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:
fuente
takeUntil
ytakeWhile
. El primero se da de baja de la fuente observable inmediatamente cuando se dispara, mientras que el último se da de baja tan pronto como la fuente observable produce el siguiente valor. Si producir un valor por la fuente observable es una operación que consume recursos, elegir entre los dos puede ir más allá de la preferencia de estilo. Vea el golpetakeUntil
vstakeWhile
, sin embargo, no para nuestro caso específico. Cuando necesitamos cancelar la suscripción de oyentes en la destrucción de componentes , solo estamos verificando el valor booleano como() => alive
entakeWhile
, por lo que no se utilizan las operaciones que consumen tiempo / memoria y la diferencia se trata básicamente del estilo (ofc, para este caso específico).Observable
, que extrae internamente algunas criptomonedas y dispara unnext
evento por cada moneda extraída, y extraer una de esas monedas lleva un día. ContakeUntil
nos daremos de baja de la minería de origenObservable
inmediatamente una vez quengOnDestroy
se llama durante la destrucción de nuestro componente. Por lo tanto, laObservable
función de minería puede cancelar su operación inmediatamente durante este proceso.takeWhile
, en elngOnDestory
solo establecemos la variable booleana. Pero laObservable
función de minería aún podría funcionar hasta por un día, y solo entonces, durante sunext
llamada, se dará cuenta de que no hay suscripciones activas y debe cancelarse.La clase de suscripción tiene una característica interesante:
Puede crear un objeto de Suscripción agregado que agrupe todas sus suscripciones. Para ello, cree una Suscripción vacía y agregue suscripciones mediante su
add()
método. Cuando su componente se destruye, solo necesita cancelar la suscripción agregada.fuente
takeUntil
enfoque oficial frente a este enfoque de recopilación de suscripciones y llamadasunsubscribe
. (Este enfoque me parece mucho más limpio.)this.subscriptions
es nulosub = subsciption.add(..).add(..)
porque en muchos casos produce resultados inesperados github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477Algunas de las mejores prácticas con respecto a las bajas de observables dentro de los componentes angulares:
Una cita de
Routing & Navigation
Y en respuesta a los siguientes enlaces:
http
observableRecopilé algunas de las mejores prácticas con respecto a las bajas de observables dentro de los componentes de Angular para compartir con ustedes:
http
la cancelación de la suscripción observable 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 lohttp
observable en sí (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 directamentehttp
desde un componente, y lahttp
respuesta tardó más de lo necesario, por lo que el usuario cerró el componente. lossubscribe()
aún se llamará al controlador 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 ).AsyncPipe
tanto como sea posible porque se da de baja automáticamente del observable en la destrucción de componentes.ActivatedRoute
observables comoroute.params
si estuvieran 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 losRouting & Navigation
documentos.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
enlace del ciclo de vida que se llamará cuando se destruya el servicio, según los documentos.takeUntil
(3) o puede usar estenpm
paquete mencionado en (4) La forma más fácil de darse de baja de Observables en Angular .FormGroup
observables comoform.valueChanges
yform.statusChanges
Renderer2
servicio comorenderer2.listen
HostListener
como angular, se preocupa bien 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 cancelará / completará automáticamente o no, agregue una
complete
devolución de llamadasubscribe(...)
y verifique si se llama cuando se destruye el componente.fuente
ngOnDestroy
se llama cuando el servicio se proporciona en un nivel diferente al nivel raíz, por ejemplo, se proporciona explícitamente en un componente que luego se elimina. En estos casos, debe darse de baja de los servicios observables internosngOnDestroy
, que siempre se llama cuando se destruyen los servicios github.com/angular/angular/commit/…Feel free to unsubscribe anyway. It is harmless and never a bad practice.
y con respecto a tu pregunta, depende. Si el componente secundario se inicia varias veces (por ejemplo, se agrega dentrongIf
o se carga dinámicamente), debe cancelar la suscripción para evitar agregar múltiples suscripciones al mismo observador. De lo contrario no es necesario. Pero prefiero cancelar la suscripción dentro del componente secundario, ya que esto lo hace más reutilizable y aislado de cómo podría usarse.Depende. Si, al llamar
someObservable.subscribe()
, comienza a retener algunos recursos que deben liberarse manualmente cuando finaliza el ciclo de vida de su componente, debe llamartheSubscription.unsubscribe()
para evitar pérdidas de memoria.Echemos un vistazo más de cerca a sus ejemplos:
getHero()
devuelve el resultado dehttp.get()
. Si observa el código fuente angular 2 ,http.get()
crea dos oyentes de eventos:y al llamar
unsubscribe()
, puede cancelar la solicitud y los oyentes:Tenga en cuenta que
_xhr
es específico de la plataforma, pero creo que es seguro asumir que es unXMLHttpRequest()
caso en su caso.Normalmente, esta es evidencia suficiente para justificar un manual
unsubscribe()
llamada . 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 omiteunsubscribe()
y permite que GC limpie a los oyentes.En cuanto a su segundo ejemplo, depende de la implementación de
params
. A partir de hoy, la guía oficial angular ya no muestra la cancelación de la suscripciónparams
. Miré en src nuevamente y descubrí queparams
es solo un BehaviorSubject . Como no se utilizaron oyentes ni temporizadores de eventos, y no se crearon variables globales, debería ser seguro omitirlosunsubscribe()
.La conclusión de su pregunta es que siempre debe actuar
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 provoque pérdidas de memoria. .En caso de duda, investigue la implementación de ese observable. Si el observable ha escrito alguna lógica de limpieza en él
unsubscribe()
, que generalmente es la función que devuelve el constructor, entonces tiene buenas razones para considerar seriamente llamarunsubscribe()
.fuente
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:
Espero que esto te ayude.
fuente
Basado en: Uso de la herencia de clase para conectar al ciclo de vida del componente Angular 2
Otro enfoque genérico:
Y use :
fuente
this.componentDestroyed$.next()
llamada como la solución aceptada porLa 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.
El archivo subscribeAndGuard.ts se usa para crear una nueva extensión Observable para envolver
.subscribe()
y dentro de ella para envolverngOnDestroy()
.El uso es el mismo que
.subscribe()
, excepto por un primer parámetro adicional que hace referencia al componente.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.
Un demo plunker está aquí
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.
fuente
Dado que la solución de seangwright (Edición 3) parece ser muy útil, también encontré un problema incluir 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 característica.
Esta respuesta proporciona una forma de liberarse de la súper llamada y hacer que "componentDestroyed $" sea un núcleo del componente base.
Y luego puede usar esta función libremente, por ejemplo:
fuente
Siguiendo la respuesta de @seangwright , he escrito una clase abstracta que maneja las suscripciones "infinitas" de observables en componentes:
Para usarlo, simplemente extiéndalo en su componente angular y llame al
subscribe()
método de la siguiente manera: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: RxJS: Don't Unsubscribe .
fuente
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:
fuente
Me gustan las dos últimas respuestas, pero experimenté un problema si la subclase a la que se hace referencia
"this"
enngOnDestroy
.Lo modifiqué para que sea esto, y parece que resolvió ese problema.
fuente
this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
En caso de que sea necesario darse de baja, se puede utilizar el siguiente operador para el método de tubería observable
se puede usar así:
El operador ajusta el método ngOnDestroy del componente.
Importante: el operador debe ser el último en tubería observable.
fuente
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:
Además, el siguiente ejemplo es un buen ejemplo de Angular para crear un componente y destruirlo después, observe cómo el componente implementa OnDestroy, si necesita onInit, también puede implementarlo en su componente, como implementos
OnInit, OnDestroy
fuente
Otra pequeña adición a las situaciones mencionadas anteriormente es:
fuente
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:
en servicio:
en el componente2:
fuente
Para manejar la suscripción, uso una clase de "Cancelar suscripción".
Aquí está la clase para darse de baja.
Y puede usar esta clase en cualquier componente / Servicio / Efecto, etc.
Ejemplo:
fuente
Puede usar la última
Subscription
clase para darse de baja del Observable con un código no tan desordenado.Podemos hacer esto,
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 comoBehavoiurSubject
ySubject
Suscripción
puedes usar esto de dos maneras,
puede enviar directamente la suscripción a la matriz de suscripción
uso
add()
deSubscription
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.. :)
fuente
El paquete SubSink, una solución fácil y consistente para darse de baja
Como nadie más lo ha mencionado, quiero recomendar el paquete Subsink creado por Ward Bell: https://github.com/wardbell/subsink#readme .
Lo he estado usando en un proyecto donde todos somos desarrolladores y todos lo estamos usando. Ayuda mucho tener una manera consistente que funcione en cada situación.
fuente
Para observables que se completan directamente después de emitir el resultado, como
AsyncSubject
por ejemplo observables de solicitudes http y tales, no es necesario darse de baja. No está de más pedirunsubscribe()
esos, pero si elclosed
método de cancelación de suscripción es observable , simplemente no hará nada :Cuando tiene observables de larga duración que emiten varios valores a lo largo del tiempo (como por ejemplo a
BehaviorSubject
o aReplaySubject
), debe cancelar la suscripción para evitar pérdidas de memoria.Puede crear fácilmente un observable que se complete directamente después de emitir un resultado de dichos observables de larga vida utilizando un operador de tubería. En algunas respuestas aquí
take(1)
se menciona la tubería. Pero prefiero lafirst()
pipa . La diferenciatake(1)
es que:Otra ventaja de la primera tubería es que puede pasar un predicado que lo ayudará a devolver el primer valor que satisfaga ciertos criterios:
Primero se completará directamente después de emitir el primer valor (o al pasar un argumento de función al primer valor que satisfaga su predicado), por lo que no es necesario darse de baja.
A veces no está seguro de si tiene un observable de larga vida o no. No digo que sea una buena práctica, pero siempre puede agregar la
first
tubería solo para asegurarse de que no necesitará darse de baja manualmente. Agregar unafirst
tubería adicional en un observable que emitirá solo un valor no hace daño.Durante el desarrollo, puede usar la
single
tubería que fallará si la fuente observable emite varios eventos. Esto puede ayudarlo a explorar el tipo de observable y si es necesario darse de baja o no.El
first
ysingle
parece muy similar, ambas tuberías pueden tomar un predicado opcional, pero las diferencias son importantes y están bien resumidas en esta respuesta de stackoverflow aquí :Tenga en cuenta que intenté ser lo más preciso y completo posible en mi respuesta con referencias a la documentación oficial, pero comente si falta algo importante ...
fuente
--- Actualización de Angular 9 y Rxjs 6 Solution
unsubscribe
en elngDestroy
ciclo de vida del componente angulartakeUntil
en RxjsngOnInit
ese momento solo ocurre una vez cuando componente init.También tenemos
async
pipa. Pero, este uso en la plantilla (no en el componente angular).fuente