6 façons de se désabonner des observables en angulaire


Le verso d'un abonnement Observable


Observables a une méthode d' abonnement qui est appelée avec une fonction de rappel pour obtenir les valeurs envoyées (émises) à l'Observable. En Angular, il est utilisé dans les composants / directives, et notamment dans le module routeur, NgRx et HTTP.


Si nous nous abonnons à un flux, il restera ouvert et sera appelé chaque fois que des valeurs de n'importe quelle partie de l'application lui seront transférées, jusqu'à ce qu'il soit fermé en appelant unsubscribe .


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

Dans cette implémentation, nous utilisons un intervalle pour envoyer des valeurs chaque seconde. Nous nous y abonnons pour obtenir la valeur envoyée, et notre fonction de rappel écrit le résultat dans la console du navigateur.


Maintenant, si AppComponent est détruit, par exemple, après avoir quitté le composant ou utilisé la méthode destroy () , nous verrons toujours le journal de la console dans le navigateur. En effet, bien que l'AppComponent ait été détruit, l'abonnement n'a pas été annulé.


Si l'abonnement n'est pas fermé, la fonction de rappel sera appelée en continu, ce qui entraînera une grave fuite de mémoire et des problèmes de performances. Afin d'éviter les fuites, il est nécessaire de se "désinscrire" de l'Observable à chaque fois.


1. Utilisation de la méthode de désabonnement


Tout abonnement a une fonction unsubscribe () pour libérer des ressources et annuler l'exécution observable. Pour éviter une fuite de mémoire, vous devez vous désabonner en utilisant la méthode de désabonnement dans Observable.


Dans Angular, vous devez vous désabonner d'Observable lorsqu'un composant est détruit. Heureusement, Angular a un crochet ngOnDestroy qui est appelé avant que le composant ne soit détruit, ce qui permet aux développeurs de s'assurer que la mémoire est nettoyée, pour éviter de figer les abonnements, d'ouvrir les ports et autres "coups de pied".


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

Nous avons ajouté ngOnDestroy à notre AppComponent et appelé la méthode unsubscribe sur Observable this.subscription . Lorsque l'AppComponent est détruit (en cliquant sur le lien, la méthode destroy () , etc.), l'abonnement ne se bloquera pas, l'intervalle sera arrêté et il n'y aura plus de journaux de console dans le navigateur.


Mais que se passe-t-il si nous avons plusieurs abonnements?


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

Il existe deux abonnements dans AppComponent et tous deux désabonnés dans le hook ngOnDestroy , empêchant une fuite de mémoire.


Vous pouvez également collecter tous les abonnements dans un tableau et vous en désabonner en boucle:


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

La méthode subscribe renvoie un objet RxJS de type Subscription. Il s'agit d'une ressource unique. Les abonnements peuvent être regroupés à l'aide de la méthode add , qui associera l'abonnement enfant à l'actuel. Lorsqu'un abonnement est annulé, tous ses enfants sont également désabonnés. Essayons de réécrire notre AppComponent:


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

Nous allons donc écrire this.subscripton1 $ et this.subscripton2 $ au moment de la destruction du composant.


2. Utilisation d'Async | Pipe


Pipe async s'abonne à Observable ou Promise et renvoie la dernière valeur transmise. Lorsqu'une nouvelle valeur est soumise, le canal asynchrone vérifie le composant pour le suivi des modifications. Si le composant est détruit, le canal asynchrone est automatiquement désabonné.


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

Lors de l'initialisation, l'AppComponent créera un observable à partir de la méthode d'intervalle. Dans le modèle $ observable , async est passé. Il souscrit à $ observable et affiche sa valeur dans le DOM. Il se désinscrira également lorsque l'AppComponent sera détruit. Le canal asynchrone de sa classe contient le crochet ngOnDestroy , il sera donc appelé lorsque sa vue sera détruite.
Pipe async est très pratique à utiliser, car lui-même s'abonnera à Unsubscribe et se désinscrira d'eux. Et maintenant, nous ne pouvons plus nous inquiéter si nous oublions de nous désinscrire dans ngOnDestroy .


3. Utilisation des instructions take * de RxJS


RxJS contient des opérateurs utiles que vous pouvez utiliser de manière déclarative pour vous désinscrire dans notre projet Angular. L'un d'eux est les opérateurs de la famille * take **:


  • prendre (n)
  • takeUntil (notifiant)
  • takeWhile (prédicat)

prendre (n)
Cet opérateur émet-il l'abonnement d'origine spécifié nombre de fois et se termine. Le plus souvent, un (1) est passé à prendre pour l'abonnement et la sortie.


Cet opérateur est utile si nous voulons que l'Observable passe une valeur une fois, puis se désabonne du flux:


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

L'abonnement $ se désabonnera lorsque l'intervalle dépassera la première valeur.


Remarque : même si l'AppComponent est détruit, l' abonnement $ n'annulera pas l'abonnement tant que l'intervalle n'aura pas dépassé la valeur. Par conséquent, il est préférable de vous assurer que tout est désabonné dans le crochet 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 (notifiant)
Cette instruction émet les valeurs de l'Observable d'origine jusqu'à ce que le notifiant envoie un message d'achèvement.


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

Nous avons un objet supplémentaire pour les notifications, qui enverra une commande pour vous désabonner de cet abonnement . Nous sommes pipe Observable en takeUntil jusqu'à ce que nous soyons signés. TakeUntil émettra des messages d'intervalle jusqu'à ce que le notifiant désabonne observable $ . Il est préférable de placer le notifiant dans le crochet ngOnDestroy .


takeWhile (prédicat)
Cette instruction émettra des valeurs observables tant qu'elles correspondent à la condition de prédicat.


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

Nous pipons $ observable avec l'opérateur takeWhile, qui enverra des valeurs tant qu'elles sont inférieures à 10. Si une valeur supérieure ou égale à 10 arrive, l'opérateur se désabonnera.
Il est important de comprendre que l'abonnement observable $ sera ouvert jusqu'à ce que l'intervalle en donne 10. Par conséquent, pour des raisons de sécurité, nous ajoutons le hook ngOnDestroy pour vous désabonner de l' observable $ lorsque le composant est détruit.


 @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. Utilisation du premier opérateur RxJS


Cette instruction est similaire à la combinaison de take (1) et takeWhile.


S'il est appelé sans paramètre, la première valeur emit-it est Observable et se termine. S'il est appelé avec une fonction de prédicat, alors emit-it est la première valeur de l'Observable d'origine, qui correspond à la condition de la fonction de prédicat, et se termine.


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

observable $ se terminera si l'intervalle passe sa première valeur. Cela signifie que dans la console, nous ne verrons qu'un seul message de journal.


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

Ici tout d'abord n'émettra pas de valeurs tant que l'intervalle ne dépassera pas 10, puis complétera $ observable . Dans la console, nous ne verrons qu'un seul message.


Dans le premier exemple, si l'AppComponent est détruit avant que le premier n'obtienne la valeur de $ observable , l'abonnement sera toujours ouvert jusqu'à ce que le premier message soit reçu.
De plus, dans le deuxième exemple, si AppComponent est détruit avant que l'intervalle renvoie une valeur correspondant à la condition de l'opérateur, l'abonnement sera toujours ouvert jusqu'à ce que l'intervalle renvoie 10. Par conséquent, pour garantir la sécurité, nous devons explicitement annuler les abonnements dans hook ngOnDestroy .


5. Utilisation de Decorator pour automatiser la désinscription


Nous sommes tous humains, nous avons tendance à oublier. La plupart des méthodes précédentes s'appuient sur le hook ngOnDestroy pour s'assurer que l'abonnement est effacé avant de détruire le composant. Mais nous pouvons oublier de les enregistrer dans ngOnDestroy - peut-être à cause de la date limite, ou du client nerveux qui sait où vous vivez ...


Dans ce cas, nous pouvons utiliser Decorators dans nos projets Angular pour vous désabonner automatiquement de tous les abonnements du composant.


Voici un exemple d'une telle mise en œuvre utile:


 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(); } } } 

Cet AutoUnsub est un décorateur qui peut être appliqué aux classes de notre projet Angular. Comme vous pouvez le voir, il enregistre le crochet ngOnDestroy d' origine, puis en crée un nouveau et le connecte à la classe à laquelle il est appliqué. De cette façon, lorsque la classe est détruite, un nouveau crochet est appelé. Sa fonction examine les propriétés de la classe, et s'il trouve un Observable, il se désabonne de lui. Ensuite, il appelle le crochet ngOnDestroy d' origine dans la classe, le cas échéant.


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

Nous l'appliquons à notre AppComponent et ne nous inquiétons plus d'oublier de se désabonner de $ observable dans ngOnDestroy - le décorateur le fera pour nous.


Mais cette méthode a aussi un inconvénient - des erreurs se produiront si notre composant a un observable sans abonnement.


6. Utilisation de tslint


Parfois, un message de tslint peut être utile pour indiquer à la console que nos composants ou directives n'ont pas déclaré la méthode ngOnDestroy . Vous pouvez ajouter une règle personnalisée à tslint pour avertir la console lorsque lint ou build est en cours d'exécution que nos composants n'ont pas le hook 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 nous avons un tel composant sans ngOnDestroy :


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

L'AppComponent linting nous avertira du crochet ngOnDestroy manquant:


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

Conclusion


Un abonnement suspendu ou ouvert peut entraîner des fuites de mémoire, des erreurs, un comportement indésirable ou de mauvaises performances des applications. Pour éviter cela, nous avons examiné différentes façons de vous désabonner des projets Observable dans Angular. Et lequel utiliser dans une situation spécifique dépend de vous.

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


All Articles