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.
-
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 -
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-destroy -
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
-
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étodongOnDestroy
.
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()
deSubscription
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:
- (1) ¿Debo darme de baja de Angular 2 Http Observables?
- (2) ¿Es necesario darse de baja de los observables creados por los métodos Http?
- (3) medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87
- (4) La forma más fácil de darse de baja de Observables en Angular
- (5) Documentación para cancelar la suscripción de RxJS
- (6) Darse de baja en un servicio no tiene sentido ya que no hay posibilidad de pérdidas de memoria
- (7) ¿Necesitamos cancelar la suscripción del observable que completa / elimina errores?
-
(8)
Un comentario sobre el
http
observable
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 dehttp
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 propiohttp
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 ahttp
desde un componente, y la respuestahttp
tardó más de lo necesario, por lo que el usuario cerró el componente. El manejadorsubscribe()
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 observablesActivatedRoute
comoroute.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 deRouting & 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 elOnDestroy
ciclo de vida deOnDestroy
, 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 packagenpm
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
comoform.valueChanges
yform.statusChanges
-
Siempre anule la suscripción de los observables del servicio
Renderer2
comorenderer2.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.