6 Möglichkeiten zum Abbestellen von Observables in Angular


Die Rückseite eines Observable-Abonnements


Observables verfügt über eine Abonnementmethode , die mit einer Rückruffunktion aufgerufen wird, um die Werte an das Observable zu senden ( auszugeben ). In Angular wird es in Komponenten / Direktiven und insbesondere im Routermodul NgRx und HTTP verwendet.


Wenn wir einen Stream abonnieren, bleibt er geöffnet und wird aufgerufen, sobald Werte aus einem beliebigen Teil der Anwendung an ihn übertragen werden, bis er durch Aufrufen von " Abbestellen" geschlossen wird.


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

In dieser Implementierung verwenden wir ein Intervall, um Werte jede Sekunde zu senden. Wir abonnieren es, um den Wert zu erhalten, und unsere Rückruffunktion schreibt das Ergebnis in die Browserkonsole.


Wenn die AppComponent beispielsweise nach dem Beenden der Komponente oder nach Verwendung der destroy () -Methode zerstört wird, wird das Konsolenprotokoll weiterhin im Browser angezeigt. Dies liegt daran, dass das Abonnement nicht gekündigt wurde, obwohl die AppComponent zerstört wurde.


Wenn das Abonnement nicht geschlossen wird, wird die Rückruffunktion kontinuierlich aufgerufen, was zu einem schwerwiegenden Speicherverlust und zu Leistungsproblemen führt. Um Undichtigkeiten zu vermeiden, müssen Sie sich jedes Mal vom Observable "abmelden".


1. Verwenden Sie die Abmeldemethode


Jedes Abonnement verfügt über eine Funktion zum Abbestellen () , mit der Ressourcen freigegeben und die Ausführung von Observable abgebrochen werden können. Um einen Speicherverlust zu vermeiden, müssen Sie das Abonnement mit der Abmeldemethode in Observable beenden.


In Angular müssen Sie Observable abbestellen, wenn eine Komponente zerstört wird. Glücklicherweise verfügt Angular über einen ngOnDestroy- Hook, der aufgerufen wird, bevor die Komponente zerstört wird. Auf diese Weise können Entwickler sicherstellen, dass der Speicher bereinigt wird, um ein Einfrieren von Abonnements, offene Ports und andere "Schussverluste" zu vermeiden.


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

Wir haben ngOnDestroy zu unserer AppComponent hinzugefügt und die Unsubscribe- Methode für Observable this.subscription aufgerufen . Wenn die AppComponent zerstört wird (durch Klicken auf den Link, die destroy () -Methode usw.), friert das Abonnement nicht ein, das Intervall wird gestoppt und im Browser werden keine Konsolenprotokolle mehr angezeigt.


Aber was ist, wenn wir mehrere Abonnements haben?


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

Es gibt zwei Abonnements in der AppComponent und beide im ngOnDestroy- Hook, wodurch ein Speicherverlust verhindert wird.


Sie können auch alle Abonnements in einem Array sammeln und sie in einer Schleife abbestellen:


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

Die Methode subscribe gibt ein RxJS-Objekt vom Typ Subscription zurück. Es ist eine einmalige Ressource. Abonnements können mit der Methode add gruppiert werden, mit der das untergeordnete Abonnement dem aktuellen Abonnement zugeordnet wird. Wenn ein Abonnement gekündigt wird, werden auch alle untergeordneten Elemente abgemeldet. Versuchen wir, unsere AppComponent neu zu schreiben:


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

Also schreiben wir this.subscripton1 $ und this.subscripton2 $ zum Zeitpunkt der Zerstörung der Komponente.


2. Verwenden von Async | Rohr


Pipe Async abonniert Observable oder Promise und gibt den letzten übergebenen Wert zurück. Wenn ein neuer Wert übergeben wird, überprüft Pipe Async die Komponente auf Änderungsnachverfolgung. Wenn die Komponente zerstört wird, wird die asynchrone Pipe automatisch abgemeldet.


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

Bei der Initialisierung erstellt die AppComponent eine Observable aus der Intervallmethode. Im beobachtbaren $ -Muster wird Async übergeben. Es abonniert observable $ und zeigt seinen Wert im DOM an. Er wird sich auch abmelden, wenn die AppComponent zerstört wird. Pipe async in seiner Klasse enthält den Hook ngOnDestroy , der aufgerufen wird, wenn seine Ansicht zerstört wird.
Die Verwendung von Pipe Async ist sehr praktisch, da er selbst die Funktion "Abbestellen" und "Abbestellen" abonniert. Und jetzt können wir uns keine Sorgen machen, wenn wir vergessen, uns von ngOnDestroy abzumelden .


3. Verwenden Sie RxJS take * -Anweisungen


RxJS enthält nützliche Operatoren, die Sie deklarativ verwenden können, um sich von unserem Angular-Projekt abzumelden. Einer von ihnen sind die Betreiber der * take ** -Familie:


  • nimm (n)
  • takeUntil (Notifier)
  • takeWhile (Prädikat)

nimm (n)
Dieser Operator gibt das Originalabonnement so oft aus, wie es angegeben wurde, und endet. In den meisten Fällen wird eine (1) zum Abonnieren und Beenden übergeben.


Dieser Operator ist nützlich, wenn Observable einen Wert einmal übergeben und dann das Abonnement für den Stream beenden soll:


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

Abonnement $ wird abbestellt, wenn das Intervall den ersten Wert überschreitet.


Bitte beachten Sie : Auch wenn die AppComponent zerstört wird, wird das Abonnement von Subscription $ nicht gekündigt, bis das Intervall den Wert überschreitet. Stellen Sie daher sicher, dass im Hook ngOnDestroy alles nicht abonniert ist :


 @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 (Notifier)
Diese Anweisung gibt die Werte aus dem ursprünglichen Observable aus, bis der Notifier eine Abschlussnachricht sendet.


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

Wir haben einen zusätzlichen Betreff für Benachrichtigungen, der einen Befehl zum Abbestellen dieses Abonnements sendet . Wir sind Pipe Observable in takeUntil bis wir unterschrieben sind. TakeUntil gibt Intervallnachrichten aus, bis der Notifier observable $ abbestellt. Es ist am besten, den Notifier in den Hook ngOnDestroy zu setzen.


takeWhile (Prädikat)
Diese Anweisung gibt Observable-Werte aus, solange sie der Prädikatbedingung entsprechen.


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

Wir leiten observable $ an den Operator takeWhile weiter, der Werte sendet, solange diese kleiner als 10 sind. Wenn ein Wert größer oder gleich 10 eintrifft, wird der Operator das Abonnement aufheben.
Es ist wichtig zu verstehen, dass das beobachtbare $ -Abonnement geöffnet ist, bis das Intervall 10 ergibt . Aus Sicherheitsgründen fügen wir den Hook ngOnDestroy hinzu, um das Abonnement von beobachtbarem $ aufzuheben , wenn die Komponente zerstört wird.


 @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. Verwenden des ersten RxJS-Operators


Diese Anweisung ähnelt der kombinierten Anweisung take (1) und takeWhile.


Wenn es ohne Parameter aufgerufen wird, ist der erste Wert von emit-it Observable und endet. Wenn es mit einer Prädikatfunktion aufgerufen wird, ist emit-it der erste Wert des ursprünglichen Observable, der der Bedingung der Prädikatfunktion entspricht, und endet.


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

beobachtbares $ endet, wenn das Intervall seinen ersten Wert überschreitet. Dies bedeutet, dass in der Konsole nur 1 Protokollmeldung angezeigt wird.


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

Hier werden erst Werte ausgegeben, wenn das Intervall 10 überschreitet, und dann wird $ beobachtbar abgeschlossen . In der Konsole sehen wir nur eine Nachricht.


Wenn im ersten Beispiel die AppComponent zerstört wird, bevor die erste den Wert von observable $ erhält, bleibt das Abonnement geöffnet, bis die erste Nachricht empfangen wird.
Wenn im zweiten Beispiel die AppComponent zerstört wird, bevor das Intervall einen Wert zurückgibt, der der Operatorbedingung entspricht, ist das Abonnement weiterhin geöffnet, bis das Intervall 10 zurückgibt. Aus Sicherheitsgründen müssen wir daher die Abonnements in explizit kündigen hook ngOnDestroy .


5. Mit Decorator das Abbestellen automatisieren


Wir sind alle Menschen, wir neigen dazu zu vergessen. Die meisten vorherigen Methoden stützen sich auf den Hook ngOnDestroy , um sicherzustellen, dass das Abonnement gelöscht wird, bevor die Komponente zerstört wird. Aber wir können vergessen, sie in ngOnDestroy zu registrieren - vielleicht wegen des Abgabetermins oder des nervösen Kunden, der weiß, wo Sie wohnen ...


In diesem Fall können wir Decorators in unseren Angular-Projekten verwenden, um das Abonnement für alle Abonnements in der Komponente automatisch zu kündigen.


Hier ist ein Beispiel für eine solche nützliche Implementierung:


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

Dieser AutoUnsub ist ein Dekorator, der auf Klassen in unserem Angular-Projekt angewendet werden kann. Wie Sie sehen, speichert es den ursprünglichen ngOnDestroy- Hook, erstellt dann einen neuen und verbindet ihn mit der Klasse, auf die es angewendet wird. Auf diese Weise wird beim Zerstören der Klasse ein neuer Hook aufgerufen. Seine Funktion durchsucht die Eigenschaften der Klasse. Wenn eine Observable gefunden wird, wird die Abmeldung von der Klasse beendet. Dann wird der ursprüngliche ngOnDestroy- Hook in der Klasse aufgerufen , falls vorhanden.


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

Wir wenden es auf unsere AppComponent an und sorgen uns nicht mehr darum, dass wir uns nicht mehr vom beobachtbaren $ in ngOnDestroy abmelden müssen - der Dekorateur wird es für uns tun.


Diese Methode hat aber auch einen Nachteil - Fehler treten auf, wenn unsere Komponente eine Observable ohne Abonnement hat.


6. Mit tslint


Manchmal kann eine Nachricht von tslint nützlich sein, um der Konsole mitzuteilen, dass unsere Komponenten oder Anweisungen die Methode ngOnDestroy nicht deklariert haben . Sie können tslint eine benutzerdefinierte Regel hinzufügen, um die Konsole zu warnen, wenn lint oder build ausgeführt wird, dass unsere Komponenten nicht über den Hook ngOnDestroy verfügen:


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

Wenn wir eine solche Komponente ohne ngOnDestroy haben :


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

Die fusselnde AppComponent warnt uns vor dem fehlenden ngOnDestroy- Hook:


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

Fazit


Ein baumelndes oder offenes Abonnement kann zu Speicherverlusten, Fehlern, unerwünschtem Verhalten oder schlechter Anwendungsleistung führen. Um dies zu vermeiden, haben wir uns verschiedene Möglichkeiten angesehen, Observable in Angular-Projekte abzubestellen. Und welches in einer bestimmten Situation verwendet werden soll, liegt bei Ihnen.

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


All Articles