6 maneras de darse de baja de los observables en angular


El reverso de una suscripción observable


Observables tiene un método de suscripción que se llama con una función de devolución de llamada para obtener los valores enviados (emitidos) al Observable. En Angular, se usa en componentes / directivas, y especialmente en el módulo enrutador, NgRx y HTTP.


Si nos suscribimos a una secuencia, permanecerá abierta y se llamará cada vez que se le transfieran valores de cualquier parte de la aplicación, hasta que se cierre al cancelar la suscripción .


@Component({...}) export class AppComponent implements OnInit { subscription: Subscription ngOnInit () { const observable = Rx.Observable.interval(1000); this.subscription = observable.subscribe(x => console.log(x)); } } 

En esta implementación, usamos un intervalo para enviar valores cada segundo. Nos suscribimos para obtener el valor enviado, y nuestra función de devolución de llamada escribe el resultado en la consola del navegador.


Ahora, si AppComponent se destruye, por ejemplo, después de salir del componente o usar el método destroy () , aún veremos el registro de la consola en el navegador. Esto se debe a que aunque el componente de aplicación se destruyó, la suscripción no se canceló.


Si no se cierra la suscripción, se llamará continuamente a la función de devolución de llamada, lo que provocará una pérdida grave de memoria y problemas de rendimiento. Para evitar fugas, debe "darse de baja" del Observable cada vez.


1. Usando el método para darse de baja


Cualquier suscripción tiene una función unsubscribe () para liberar recursos y cancelar la ejecución de Observable. Para evitar una pérdida de memoria, debe darse de baja utilizando el método de cancelación de suscripción en Observable.


En Angular, debe darse de baja de Observable cuando se destruye un componente. Afortunadamente, Angular tiene un gancho ngOnDestroy que se llama antes de que se destruya el componente, lo que permite a los desarrolladores asegurarse de que se limpia la memoria, para evitar congelar suscripciones, abrir puertos y otros "disparos en el pie".


 @Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription: Subscription ngOnInit () { const observable = Rx.Observable.interval(1000); this.subscription = observable.subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription.unsubscribe() } } 

Agregamos ngOnDestroy a nuestro AppComponent y llamamos al método de cancelación de suscripción en Observable this.subscription . Cuando se destruye el componente de aplicación (haciendo clic en el enlace, el método destroy () , etc.), la suscripción no se congelará, el intervalo se detendrá y no habrá más registros de consola en el navegador.


Pero, ¿y si tenemos varias suscripciones?


 @Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription1$: Subscription; subscription2$: Subscription; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); } ngOnDestroy() { this.subscription1$.unsubscribe(); this.subscription2$.unsubscribe(); } } 

Hay dos suscripciones en AppComponent y ambas están dadas de baja en el enlace ngOnDestroy , evitando una pérdida de memoria.


También puede recopilar todas las suscripciones en una matriz y darse de baja de ellas en un bucle:


 @Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription1$: Subscription; subscription2$: Subscription; subscriptions: Subscription[] = []; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); this.subscriptions.push(this.subscription1$); this.subscriptions.push(this.subscription2$); } ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } } 

El método de suscripción devuelve un objeto RxJS de tipo Suscripción. Es un recurso de una sola vez. Las suscripciones se pueden agrupar utilizando el método add , que adjuntará la suscripción secundaria a la actual. Cuando se cancela una suscripción, todos sus hijos también se anulan. Intentemos reescribir nuestro componente de aplicación:


 @Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription: Subscription; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); const subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); const subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); this.subscription.add(subscription1$); this.subscription.add(subscription2$); } ngOnDestroy() { this.subscription.unsubscribe() } } 

Entonces escribiremos this.subscripton1 $ y this.subscripton2 $ en el momento de la destrucción del componente.


2. Usando Async | Tubo


Pipe async se suscribe a Observable o Promise y devuelve el último valor pasado. Cuando se envía un nuevo valor, Pipe Async verifica el componente para el seguimiento de cambios. Si el componente se destruye, la tubería asíncrona se cancela automáticamente.


 @Component({ ..., template: ` <div> Interval: {{observable$ | async}} </div> ` }) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); } } 

Tras la inicialización, AppComponent creará un Observable a partir del método de intervalo. En el patrón $ observable , se pasa asíncrono . Se suscribe a $ observables y muestra su valor en el DOM. También se dará de baja cuando se destruya el componente de aplicación. Pipe async en su clase contiene el gancho ngOnDestroy , por lo que se llamará cuando se destruya su vista.
Pipe async es muy conveniente de usar, porque él mismo se suscribirá a Unsubscribe y se dará de baja de ellos. Y ahora no podemos preocuparnos si olvidamos cancelar la suscripción en ngOnDestroy .


3. Uso de las declaraciones take * de RxJS


RxJS contiene operadores útiles que puede usar de manera declarativa para darse de baja en nuestro proyecto Angular. Uno de ellos son los operadores de la familia * take **:


  • tomar (n)
  • takeUntil (notificador)
  • takeWhile (predicado)

tomar (n)
Este operador emite la suscripción original especificada el número de veces y finaliza. Muy a menudo, se pasa uno (1) para tomar la suscripción y salir.


Este operador es útil si queremos que el Observable pase un valor una vez y luego se dé de baja de la transmisión:


 @Component({...}) export class AppComponent implements OnInit { subscription$; ngOnInit () { const observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(take(1)). subscribe(x => console.log(x)); } } 

suscripción $ se dará de baja cuando el intervalo pase el primer valor.


Tenga en cuenta : incluso si el componente de aplicación se destruye, la suscripción $ no cancelará la suscripción hasta que el intervalo pase el valor. Por lo tanto, es mejor asegurarse de que todo esté anulado en el enlace ngOnDestroy :


 @Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription$; ngOnInit () { var observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(take(1)).subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription$.unsubscribe(); } } 

takeUntil (notificador)
Esta declaración emite los valores del Observable original hasta que el notificador envía un mensaje de finalización.


 @Component({…}) export class AppComponent implements OnInit, OnDestroy { notifier = new Subject(); ngOnInit () { const observable$ = Rx.Observable.interval(1000); observable$.pipe(takeUntil(this.notifier)).subscribe(x => console.log(x)); } ngOnDestroy() { this.notifier.next(); this.notifier.complete(); } } 

Tenemos un Asunto adicional para notificaciones, que enviará un comando para cancelar esta suscripción. Somos pipe Observable en takeUntil hasta que estemos firmados. TakeUntil emitirá mensajes de intervalo hasta que el notificador cancele la suscripción de $ observables . Es mejor poner el notificador en el gancho ngOnDestroy .


takeWhile (predicado)
Esta declaración emitirá valores observables siempre que coincidan con la condición del predicado.


 @Component({...}) export class AppComponent implements OnInit { ngOnInit () { const observable$ = Rx.Observable.interval(1000); observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x)); } } 

Pip $ observables con el operador takeWhile, que enviará valores siempre que sean menores que 10. Si llega un valor mayor o igual a 10, el operador se dará de baja.
Es importante comprender que la suscripción $ observable estará abierta hasta que el intervalo 10. Por lo tanto, por seguridad, agregamos el enlace ngOnDestroy para cancelar la suscripción de $ observable cuando se destruye el componente.


 @Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription$; ngOnInit () { var observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription$.unsubscribe(); } } 

4. Usando el primer operador RxJS


Esta declaración es similar a la combinación de take (1) y takeWhile.


Si se llama sin un parámetro, el primer valor de emit-it es Observable y finaliza. Si se llama con una función de predicado, entonces emit-it es el primer valor del Observable original, que corresponde a la condición de la función de predicado, y termina.


 @Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable = Rx.Observable.interval(1000); this.observable$.pipe(first()).subscribe(x => console.log(x)); } } 

$ observable finalizará si el intervalo pasa su primer valor. Esto significa que en la consola solo veremos 1 mensaje de registro.


 @Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.pipe(first(val => val === 10)).subscribe(x => console.log(x)); } } 

Aquí primero no emitirá valores hasta que el intervalo pase 10, y luego complete $ observable . En la consola solo veremos un mensaje.


En el primer ejemplo, si el componente de aplicación se destruye antes de que el primero obtenga el valor de $ observable , la suscripción seguirá abierta hasta que se reciba el primer mensaje.
Además, en el segundo ejemplo, si AppComponent se destruye antes de que el intervalo devuelva un valor que coincida con la condición del operador, la suscripción seguirá abierta hasta que el intervalo devuelva 10. Por lo tanto, para garantizar la seguridad, debemos cancelar explícitamente las suscripciones en gancho ngOnDestroy .


5. Usando Decorator para automatizar la cancelación de la suscripción


Todos somos humanos, tendemos a olvidar. La mayoría de los métodos anteriores se basan en el enlace ngOnDestroy para asegurarse de que la suscripción se borre antes de destruir el componente. Pero podemos olvidar registrarlos en ngOnDestroy , tal vez debido a la fecha límite, o al cliente nervioso que sabe dónde vives ...


En este caso, podemos usar decoradores en nuestros proyectos angulares para cancelar automáticamente la suscripción de todas las suscripciones en el componente.


Aquí hay un ejemplo de una implementación tan útil:


 function AutoUnsub() { return function(constructor) { const orig = constructor.prototype.ngOnDestroy; constructor.prototype.ngOnDestroy = function() { for(let prop in this) { const property = this[prop]; if(typeof property.subscribe === "function") { property.unsubscribe(); } } orig.apply(); } } } 

Este AutoUnsub es un decorador que se puede aplicar a clases en nuestro proyecto Angular. Como puede ver, guarda el gancho original ngOnDestroy , luego crea uno nuevo y lo conecta a la clase a la que se aplica. De esa manera, cuando se destruye la clase, se llama un nuevo gancho. Su función examina las propiedades de la clase y, si encuentra un Observable, se da de baja de él. Luego llama al gancho original ngOnDestroy en la clase, si lo hay.


 @Component({...}) @AutoUnsub export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.subscribe(x => console.log(x)) } } 

Lo aplicamos a nuestro componente de aplicación y ya no nos preocupamos de olvidarnos de cancelar la suscripción de $ observable en ngOnDestroy ; el decorador lo hará por nosotros.


Pero este método también tiene un inconveniente: se producirán errores si nuestro componente tiene un Observable sin una suscripción.


6. Usando tslint


A veces, un mensaje de tslint puede ser útil para decirle a la consola que nuestros componentes o directivas no han declarado el método ngOnDestroy . Puede agregar una regla personalizada a tslint para advertir a la consola cuando se ejecuta lint o build que nuestros componentes no tienen el enlace ngOnDestroy :


 // ngOnDestroyRule.tsimport * as Lint from "tslint" import * as ts from "typescript"; import * as tsutils from "tsutils"; export class Rule extends Lint.Rules.AbstractRule { public static metadata: Lint.IRuleMetadata = { ruleName: "ng-on-destroy", description: "Enforces ngOnDestory hook on component/directive/pipe classes", optionsDescription: "Not configurable.", options: null, type: "style", typescriptOnly: false } public static FAILURE_STRING = "Class name must have the ngOnDestroy hook"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new NgOnDestroyWalker(sourceFile, Rule.metadata.ruleName, void this.getOptions())) } } class NgOnDestroyWalker extends Lint.AbstractWalker { visitClassDeclaration(node: ts.ClassDeclaration) { this.validateMethods(node); } validateMethods(node: ts.ClassDeclaration) { const methodNames = node.members.filter(ts.isMethodDeclaration).map(m => m.name!.getText()); const ngOnDestroyArr = methodNames.filter( methodName => methodName === "ngOnDestroy"); if( ngOnDestroyArr.length === 0) this.addFailureAtNode(node.name, Rule.FAILURE_STRING); } } 

Si tenemos dicho componente sin ngOnDestroy :


 @Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.subscribe(x => console.log(x)); } } 

El componente de aplicación de pelusa nos advertirá sobre el gancho de ngOnDestroy que falta:


 $ ng lint Error at app.component.ts 12: Class name must have the ngOnDestroy hook 

Conclusión


Una suscripción abierta o suspendida puede provocar pérdidas de memoria, errores, comportamiento no deseado o bajo rendimiento de la aplicación. Para evitar esto, observamos diferentes formas de cancelar la suscripción de los proyectos Observable en Angular. Y cuál usar en una situación específica depende de usted.

Source: https://habr.com/ru/post/484762/


All Articles