Angular / RxJs Wann sollte ich mich von `Subscription` abmelden?

720

Wann sollte ich die SubscriptionInstanzen speichern und unsubscribe()während des NgOnDestroy-Lebenszyklus aufrufen und wann kann ich sie einfach ignorieren?

Das Speichern aller Abonnements führt zu viel Chaos im Komponentencode.

HTTP-Client-Handbuch ignoriert Abonnements wie folgt:

getHeroes() {
  this.heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

In der gleichen Zeit sagt Route & Navigation Guide , dass:

Irgendwann werden wir woanders navigieren. Der Router entfernt diese Komponente aus dem DOM und zerstört sie. Wir müssen nach uns selbst aufräumen, bevor das passiert. Insbesondere müssen wir uns abmelden, bevor Angular die Komponente zerstört. Andernfalls kann es zu einem Speicherverlust kommen.

Wir melden uns von unserer Observablein der ngOnDestroyMethode ab.

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();
}
Sergey Tihon
quelle
20
Ich denke, Subscriptions to http-requestskann ignoriert werden, da sie nur onNexteinmal anrufen und dann anrufen onComplete. Der Routerruft stattdessen onNextwiederholt an und ruft möglicherweise nie an onComplete(nicht sicher ...). Gleiches gilt für Observables von Events. Also ich denke das sollte sein unsubscribed.
Springrbua
1
@ gt6707a Der Stream wird unabhängig von einer Beobachtung dieser Fertigstellung abgeschlossen (oder nicht abgeschlossen). Die Rückrufe (der Beobachter), die für die Abonnementfunktion bereitgestellt werden, bestimmen nicht, ob Ressourcen zugewiesen sind. Es ist der Aufruf an sich subscribeselbst, der potenziell Ressourcen vorgelagert zuweist.
Seangwright

Antworten:

981

--- Bearbeiten 4 - Zusätzliche Ressourcen (2018/09/01)

In einer kürzlich erschienenen Folge von Adventures in Angular diskutieren Ben Lesh und Ward Bell die Fragen, wie / wann eine Komponente abgemeldet werden muss. Die Diskussion beginnt gegen 1:05:30 Uhr.

Ward erwähnt right now there's an awful takeUntil dance that takes a lot of machineryund Shai Reznik erwähnt Angular handles some of the subscriptions like http and routing.

Als Antwort darauf erwähnt Ben, dass es derzeit Diskussionen gibt, die es Observables ermöglichen, sich in die Lebenszyklusereignisse der Angular-Komponente einzubinden, und Ward schlägt eine Observable von Lebenszyklusereignissen vor, die eine Komponente abonnieren könnte, um zu wissen, wann Observables abgeschlossen werden müssen, die als interner Status der Komponente verwaltet werden.

Das heißt, wir brauchen jetzt meistens Lösungen. Hier sind einige andere Ressourcen.

  1. Eine Empfehlung für das takeUntil()Muster von RxJs Kernteammitglied Nicholas Jamieson und eine tslint-Regel, um es durchzusetzen. https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Leichtes npm-Paket, das einen Observable-Operator verfügbar macht, der eine Komponenteninstanz ( this) als Parameter verwendet und sich währenddessen automatisch abmeldet ngOnDestroy. https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Eine weitere Variante des oben genannten mit etwas besserer Ergonomie, wenn Sie keine AOT-Builds ausführen (aber wir sollten jetzt alle AOT ausführen). https://github.com/smnbbrv/ngx-rx-collector

  4. Benutzerdefinierte Direktive *ngSubscribe, die wie eine asynchrone Pipe funktioniert, jedoch eine eingebettete Ansicht in Ihrer Vorlage erstellt, sodass Sie in Ihrer Vorlage auf den Wert "Unwrapped" verweisen können. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

In einem Kommentar zu Nicholas 'Blog erwähne ich, dass eine Überbeanspruchung takeUntil()ein Zeichen dafür sein könnte, dass Ihre Komponente versucht, zu viel zu tun, und dass die Trennung Ihrer vorhandenen Komponenten in Feature- und Präsentationskomponenten in Betracht gezogen werden sollte. Sie können dann | asyncdas Observable von der Feature-Komponente in eine Inputder Presentational-Komponenten umwandeln, was bedeutet, dass nirgendwo Abonnements erforderlich sind. Lesen Sie mehr über diesen Ansatz hier

--- Edit 3 - Die 'offizielle' Lösung (2017/04/09)

Ich habe mit Ward Bell über diese Frage bei NGConf gesprochen (ich habe ihm sogar diese Antwort gezeigt, die er für richtig hielt), aber er hat mir gesagt, dass das Docs-Team für Angular eine Lösung für diese Frage hat, die nicht veröffentlicht wurde (obwohl sie daran arbeiten, sie zu genehmigen ). Er sagte mir auch, ich könnte meine SO-Antwort mit der bevorstehenden offiziellen Empfehlung aktualisieren.

Die Lösung, die wir alle in Zukunft verwenden sollten, besteht darin private ngUnsubscribe = new Subject();, allen Komponenten ein Feld hinzuzufügen, deren Klassencode s .subscribe()aufruft Observable.

Wir rufen dann this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();unsere ngOnDestroy()Methoden auf.

Die geheime Sauce (wie bereits von @metamaker erwähnt ) besteht darin, takeUntil(this.ngUnsubscribe)vor jedem unserer .subscribe()Anrufe anzurufen, um sicherzustellen , dass alle Abonnements bereinigt werden, wenn die Komponente zerstört wird.

Beispiel:

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

Hinweis: Es ist wichtig, den takeUntilBediener als letzten hinzuzufügen , um Undichtigkeiten mit dazwischen liegenden Observablen in der Bedienerkette zu vermeiden.

--- Bearbeiten 2 (28.12.2016)

Quelle 5

Im Angular-Lernprogramm im Kapitel "Routing" heißt es nun: "Der Router verwaltet die von ihm bereitgestellten Observables und lokalisiert die Abonnements. Die Abonnements werden bereinigt, wenn die Komponente zerstört wird, und schützen so vor Speicherlecks, sodass wir uns nicht abmelden müssen." die Routenparameter beobachtbar. " - Mark Rajcok

Hier ist eine Diskussion über die Github-Probleme in den Angular-Dokumenten zu Router Observables, in denen Ward Bell erwähnt, dass eine Klärung all dessen in Arbeit ist.

--- Bearbeiten 1

Quelle 4

In diesem Video von NgEurope sagt Rob Wormald auch, dass Sie Router Observables nicht abbestellen müssen. Er erwähnt auch den httpService und ActivatedRoute.paramsin diesem Video vom November 2016 .

--- Ursprüngliche Antwort

TLDR:

Für diese Frage gibt es (2) Arten von Observables- endlichem Wert und unendlichem Wert.

http Observableserzeugen endliche (1) Werte und so etwas wie ein DOM event listener Observableserzeugen unendliche Werte.

Wenn Sie manuell aufrufen subscribe(ohne asynchrone Pipe), dann unsubscribevon unendlich Observables .

Mach dir keine Sorgen um endliche , RxJswerde dich um sie kümmern.

Quelle 1

Ich habe hier eine Antwort von Rob Wormald in Angulars Gitter gefunden .

Er sagt (ich habe mich aus Gründen der Klarheit neu organisiert und die Betonung liegt bei mir)

Wenn es sich um eine Einzelwertsequenz handelt (wie bei einer http-Anforderung), ist die manuelle Bereinigung nicht erforderlich (vorausgesetzt, Sie abonnieren den Controller manuell).

Ich sollte sagen "wenn es eine Sequenz ist, die abgeschlossen ist " (von denen Einzelwertsequenzen, a la http, eine sind)

Wenn es sich um eine unendliche Sequenz handelt , sollten Sie sich abmelden, was die asynchrone Pipe für Sie erledigt

Außerdem erwähnt er in diesem Youtube-Video zu Observables, dass they clean up after themselves... im Zusammenhang mit Observables, die complete(wie Promises, die immer vollständig sind, weil sie immer einen Wert produzieren und enden) - wir haben uns nie Sorgen gemacht, Promises abzumelden, um sicherzustellen, dass sie das xhrEreignis bereinigen Zuhörer, richtig?).

Quelle 2

Auch in der Rangle-Anleitung zu Angular 2 heißt es

In den meisten Fällen müssen wir die Abmeldemethode nicht explizit aufrufen, es sei denn, wir möchten vorzeitig kündigen oder unser Observable hat eine längere Lebensdauer als unser Abonnement. Das Standardverhalten von Observable-Operatoren besteht darin, das Abonnement zu entsorgen, sobald .complete () - oder .error () -Nachrichten veröffentlicht werden. Denken Sie daran, dass RxJS die meiste Zeit so konzipiert wurde, dass es "Feuer und Vergessen" verwendet.

Wann gilt der Satz our Observable has a longer lifespan than our subscription?

Dies gilt, wenn ein Abonnement in einer Komponente erstellt wird, die vor (oder nicht lange vor) dem ObservableAbschluss zerstört wird.

Ich habe dies als Bedeutung gelesen, wenn wir eine httpAnfrage oder ein Observable abonnieren , das 10 Werte ausgibt und unsere Komponente zerstört wird, bevor diese httpAnfrage zurückkehrt oder die 10 Werte ausgegeben wurden, sind wir immer noch in Ordnung!

Wenn die Anforderung zurückkehrt oder der 10. Wert endgültig ausgegeben Observablewird, ist der Vorgang abgeschlossen und alle Ressourcen werden bereinigt.

Quelle 3

Wenn wir uns dieses Beispiel aus derselben Rangle-Anleitung ansehen, können wir sehen, dass für das Subscriptionto ein route.paramserforderlich ist, unsubscribe()da wir nicht wissen, wann sich diese paramsnicht mehr ändern (neue Werte ausgeben ).

Die Komponente könnte durch Wegnavigation zerstört werden. In diesem Fall ändern sich die Routenparameter wahrscheinlich noch (sie können sich technisch bis zum Ende der App ändern), und die im Abonnement zugewiesenen Ressourcen werden weiterhin zugewiesen, da keine vorhanden ist completion.

Seangwright
quelle
15
Ein Anruf complete()von selbst scheint die Abonnements nicht zu bereinigen. Ich glaube jedoch, dass das Aufrufen next()und dann complete()das Aufhören takeUntil()nur dann beendet wird, wenn ein Wert erzeugt wird, nicht wenn die Sequenz beendet ist.
Firefly
2
@seangwright Ein schneller Test mit einem Mitglied des Typs Subjectinnerhalb einer Komponente und das Umschalten mit, ngIfum auszulösen ngOnInitund zu ngOnDestroyzeigen, dass der Betreff und seine Abonnements niemals abgeschlossen oder entsorgt werden (ein finallyOperator mit dem Abonnement verbunden). Ich nenne muss Subject.complete()in ngOnDestroy, so dass die Zeichnungen nach sich aufzuräumen können.
Lars
3
Dein --- Edit 3 ist sehr aufschlussreich, danke! Ich habe nur eine Folgefrage: Wenn takeUnitlwir den Ansatz verwenden, müssen wir uns nie manuell von Observablen abmelden? Ist das der Fall? Darüber hinaus warum brauchen wir Anruf next()in die ngOnDestroy, warum nicht einfach anrufen complete()?
hässlicher Code
6
@ Seangwright Das ist enttäuschend; Die zusätzliche Heizplatte ist nervig.
Spongessuck
3
Bearbeiten Sie 3 im Zusammenhang mit Ereignissen unter medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87
HankCa
90

Sie müssen keine Abonnements haben und sich manuell abmelden. Verwenden Sie die Kombination aus Betreff und takeUntil , um Abonnements wie ein Chef zu behandeln:

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}

Der alternative Ansatz , der von @acumartini in Kommentaren vorgeschlagen wurde , verwendet takeWhile anstelle von takeUntil . Sie mögen es vorziehen, aber denken Sie daran, dass auf diese Weise Ihre Observable-Ausführung bei ngDestroy Ihrer Komponente nicht abgebrochen wird (z. B. wenn Sie zeitaufwändige Berechnungen durchführen oder auf Daten vom Server warten). Die auf takeUntil basierende Methode hat diesen Nachteil nicht und führt zur sofortigen Stornierung der Anfrage. Vielen Dank an @AlexChe für die ausführliche Erklärung in den Kommentaren .

Also hier ist der Code:

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(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
  }
}
Metamaker
quelle
1
Wenn er nur einen Bool verwendet, um den Status beizubehalten, wie funktioniert "takeUntil" wie erwartet?
Val
5
Ich denke, es gibt einen signifikanten Unterschied zwischen takeUntilund takeWhile. Ersteres wird von der beobachtbaren Quelle sofort nach dem Auslösen abgemeldet, während letzteres nur dann abgemeldet wird, wenn der nächste Wert von der beobachtbaren Quelle erzeugt wird. Wenn das Erzeugen eines Werts durch die beobachtbare Quelle eine ressourcenintensive Operation ist, kann die Auswahl zwischen beiden über die Stilpräferenz hinausgehen. Siehe das Plunk
Alex Che
1
@AlexChe danke für die Bereitstellung von interessantem Plunk! Dies ist ein sehr gültiger Punkt für die allgemeine Verwendung von takeUntilvs takeWhile, jedoch nicht für unseren speziellen Fall. Wenn wir Listener für die Zerstörung von Komponenten abbestellen müssen, überprüfen wir nur den booleschen Wert wie () => alivein takeWhile, sodass keine zeit- / speicherintensiven Operationen verwendet werden und der Unterschied ziemlich stark vom Styling abhängt (ofc für diesen speziellen Fall).
Metamaker
1
@metamaker Sagen wir, in unserer Komponente abonnieren wir eine Observable, die intern Kryptowährung abbaut und ein nextEreignis für jede abgebaute Münze auslöst. Das Abbauen einer solchen Münze dauert einen Tag. Mit werden takeUntilwir uns Observablesofort vom Source Mining abmelden, sobald ngOnDestroyes während unserer Komponentenzerstörung aufgerufen wird. Somit kann die Mining- ObservableFunktion ihren Betrieb während dieses Vorgangs sofort abbrechen.
Alex Che
1
OTOH, wenn wir verwenden takeWhile, in der ngOnDestorywir die Boolesche Variable gerade eingestellt haben . Die Mining- ObservableFunktion funktioniert jedoch möglicherweise noch bis zu einem Tag. Erst dann wird beim nextAufrufen festgestellt, dass keine Abonnements aktiv sind und gekündigt werden müssen.
Alex Che
76

Die Abonnementklasse hat eine interessante Funktion:

Stellt eine verfügbare Ressource dar, z. B. die Ausführung eines Observable. Ein Abonnement hat eine wichtige Methode, das Abbestellen, die kein Argument akzeptiert und nur die vom Abonnement gehaltene Ressource entsorgt.
Darüber hinaus können Abonnements über die add () -Methode zusammengefasst werden, mit der dem aktuellen Abonnement ein untergeordnetes Abonnement zugeordnet wird. Wenn ein Abonnement abgemeldet wird, werden auch alle seine Kinder (und Enkelkinder) abgemeldet.

Sie können ein aggregiertes Abonnementobjekt erstellen, das alle Ihre Abonnements gruppiert. Sie tun dies, indem Sie ein leeres Abonnement erstellen und es mithilfe seiner add()Methode hinzufügen . Wenn Ihre Komponente zerstört wird, müssen Sie nur das Gesamtabonnement abbestellen.

@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();
  }
}
Steven Liekens
quelle
Ich benutze diesen Ansatz. Sie fragen sich, ob dies besser ist als die Verwendung des Ansatzes mit takeUntil (), wie in der akzeptierten Antwort. Nachteile?
Manuel Di Iorio
Keine mir bekannten Nachteile. Ich denke nicht, dass das besser ist, nur anders.
Steven Liekens
2
Weitere Informationen zum offiziellen takeUntilAnsatz im Vergleich zu diesem Ansatz zum Sammeln von Abonnements und Anrufen finden Sie unter medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87unsubscribe . (Dieser Ansatz scheint mir viel sauberer zu sein.)
Josh Kelley
3
Ein kleiner Vorteil dieser Antwort: Sie müssen nicht überprüfen, ob this.subscriptionsnull ist
user2023861
1
Vermeiden Sie einfach die Verkettung von Add-Methoden, sub = subsciption.add(..).add(..)da dies in vielen Fällen zu unerwarteten Ergebnissen führt. Github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477
Evgeniy Generalov
32

Einige der Best Practices für das Abbestellen von Observablen in Angular-Komponenten:

Ein Zitat aus Routing & Navigation

Wenn Sie ein Observable in einer Komponente abonnieren, können Sie sich fast immer abmelden, wenn die Komponente zerstört wird.

Es gibt einige außergewöhnliche Observable, bei denen dies nicht erforderlich ist. Die ActivatedRoute-Observablen gehören zu den Ausnahmen.

Die ActivatedRoute und ihre Observablen sind vom Router selbst isoliert. Der Router zerstört eine geroutete Komponente, wenn sie nicht mehr benötigt wird, und die injizierte ActivatedRoute stirbt damit.

Fühlen Sie sich trotzdem frei, sich abzumelden. Es ist harmlos und niemals eine schlechte Praxis.

Und bei der Beantwortung der folgenden Links:

Ich habe einige der Best Practices für das Abbestellen von Observablen in Angular-Komponenten gesammelt, um sie mit Ihnen zu teilen:

  • httpEine beobachtbare Abmeldung ist an Bedingungen geknüpft, und wir sollten die Auswirkungen des "Abonnement-Rückrufs" berücksichtigen, der ausgeführt wird, nachdem die Komponente von Fall zu Fall zerstört wurde. Wir wissen, dass der Winkel das httpBeobachtbare selbst abmeldet und reinigt (1) , (2) . Dies gilt zwar aus Sicht der Ressourcen, erzählt aber nur die halbe Geschichte. Nehmen wir an, wir sprechen über einen direkten Anrufhttp aus einer Komponente heraus, und die httpAntwort dauerte länger als erforderlich, sodass der Benutzer die Komponente geschlossen hat. Dassubscribe()Der Handler wird auch dann aufgerufen, wenn die Komponente geschlossen und zerstört wird. Dies kann unerwünschte Nebenwirkungen haben und in den schlimmsten Szenarien den Anwendungsstatus unterbrochen lassen. Es kann auch Ausnahmen verursachen, wenn der Code im Rückruf versucht, etwas aufzurufen, das gerade entsorgt wurde. Gleichzeitig sind sie jedoch gelegentlich erwünscht. Nehmen wir an, Sie erstellen einen E-Mail-Client und lösen einen Sound aus, wenn die E-Mail gesendet wurde. Nun, Sie möchten, dass dies auch dann geschieht, wenn die Komponente geschlossen ist ( 8 ).
  • Sie müssen sich nicht von Observables abmelden, die vollständig oder fehlerhaft sind. Dies schadet jedoch nicht (7) .
  • Verwenden Sie AsyncPipeso viel wie möglich, da sich das bei der Zerstörung von Komponenten beobachtbare Element automatisch abmeldet.
  • Melden Sie sich von den ActivatedRouteObservablen ab, z. B. route.paramswenn sie in einer verschachtelten (in tpl mit dem Komponentenselektor hinzugefügt) oder dynamischen Komponente abonniert sind, da sie möglicherweise mehrmals abonniert werden, solange die übergeordnete / Host-Komponente vorhanden ist. Sie müssen sie in anderen Szenarien nicht abbestellen, wie im obigen Zitat aus den Routing & NavigationDokumenten erwähnt.
  • Abbestellen von globalen Observablen, die von Komponenten gemeinsam genutzt werden, die beispielsweise über einen Angular-Dienst verfügbar gemacht werden, da sie möglicherweise mehrmals abonniert werden, solange die Komponente initialisiert wird.
  • Es ist nicht erforderlich, sich von internen Observables eines Dienstes mit Anwendungsbereich abzumelden, da dieser Dienst niemals zerstört wird. Wenn Ihre gesamte Anwendung nicht zerstört wird, gibt es keinen wirklichen Grund, sich abzumelden, und es besteht keine Möglichkeit von Speicherlecks. (6) .

    Hinweis: In Bezug auf Dienste mit Gültigkeitsbereich, dh Komponentenanbieter, werden diese zerstört, wenn die Komponente zerstört wird. In diesem Fall sollten wir, wenn wir ein innerhalb dieses Anbieters beobachtbares Objekt abonnieren, in Betracht ziehen, es mit dem OnDestroyLifecycle-Hook abzubestellen, der gemäß den Dokumenten aufgerufen wird, wenn der Dienst zerstört wird.
  • Verwenden Sie eine abstrakte Technik, um Code-Chaos zu vermeiden, das durch das Abbestellen entstehen kann. Sie können Ihre Abonnements mit takeUntil (3) verwalten oder dieses unter (4) genannte npm Paket verwenden. Der einfachste Weg, sich von Observables in Angular abzumelden .
  • FormGroupBeobachten Sie immer Observables wie form.valueChangesundform.statusChanges
  • Immer abmelden von Observables of Renderer2Service wierenderer2.listen
  • Melden Sie sich von jedem anderen beobachtbaren Element als Speicherleck-Schutzschritt ab, bis Angular Docs uns ausdrücklich mitteilt, welche beobachtbaren Elemente nicht abgemeldet werden müssen (Problem überprüfen : (5) Dokumentation zum Abbestellen von RxJS (offen) ).
  • Bonus: Verwenden Sie immer die Angular-Methoden, um Ereignisse zu binden, HostListenerda Angular sich bei Bedarf gut um das Entfernen der Ereignis-Listener kümmert und potenzielle Speicherverluste aufgrund von Ereignisbindungen verhindert.

Ein schöner letzter Tipp : Wenn Sie nicht wissen, ob ein Observable automatisch abgemeldet / abgeschlossen wird oder nicht, fügen Sie einen completeRückruf hinzu subscribe(...)und prüfen Sie, ob es aufgerufen wird, wenn die Komponente zerstört wird.

Mouneer
quelle
Antwort für Nr. 6 ist nicht ganz richtig. Dienste werden zerstört und ngOnDestroyaufgerufen, wenn der Dienst auf einer anderen Ebene als der Stammebene bereitgestellt wird, z. B. explizit in einer Komponente, die später entfernt wird. In diesen Fällen sollten Sie sich von den inneren Observablen des Dienstes abmelden
Drenai
@Drenai, danke für deinen Kommentar und höflich stimme ich nicht zu. Wenn eine Komponente zerstört wird, werden die Komponente, der Dienst und das Observable alle GCed und die Abmeldung ist in diesem Fall unbrauchbar, es sei denn, Sie halten eine Referenz für das Observable irgendwo von der Komponente entfernt (was nicht logisch ist, um die Komponentenzustände global zu verlieren trotz Umfang des Dienstes an der Komponente)
Mouneer
Wenn der zerstörte Dienst ein Abonnement für eine beobachtbare Datei hat, die zu einem anderen Dienst weiter oben in der DI-Hierarchie gehört, wird keine GC ausgeführt. Vermeiden Sie dieses Szenario, indem Sie sich abmelden. Dies ngOnDestroywird immer aufgerufen, wenn Dienste zerstört werden. Github.com/angular/angular/commit/…
Drenai
1
@ Drenai, Überprüfen Sie die aktualisierte Antwort.
Mouneer
2
@Tim Zunächst einmal Feel free to unsubscribe anyway. It is harmless and never a bad practice.und in Bezug auf Ihre Frage kommt es darauf an. Wenn die untergeordnete Komponente mehrmals initiiert wird (z. B. innerhalb hinzugefügt ngIfoder dynamisch geladen wird), müssen Sie sich abmelden, um zu vermeiden, dass demselben Beobachter mehrere Abonnements hinzugefügt werden. Sonst keine Notwendigkeit. Ich bevorzuge es jedoch, die untergeordnete Komponente abzubestellen, da sie dadurch wiederverwendbarer und isolierter wird, wie sie verwendet werden könnte.
Mouneer
18

Es hängt davon ab, ob. Wenn Sie durch Aufrufen someObservable.subscribe()eine Ressource halten, die manuell freigegeben werden muss, wenn der Lebenszyklus Ihrer Komponente abgelaufen ist, sollten Sie aufrufen theSubscription.unsubscribe(), um Speicherverluste zu vermeiden.

Schauen wir uns Ihre Beispiele genauer an:

getHero()gibt das Ergebnis von zurück http.get(). Wenn Sie sich den Quellcodehttp.get() für Winkel 2 ansehen , werden zwei Ereignis-Listener erstellt:

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

und indem Sie anrufen unsubscribe(), können Sie die Anfrage sowie die Zuhörer abbrechen:

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

Beachten Sie, dass dies _xhrplattformspezifisch ist, aber ich denke, es ist sicher anzunehmen, dass es sich um eine XMLHttpRequest()in Ihrem Fall handelt.

Normalerweise ist dies ein ausreichender Beweis, um einen manuellen unsubscribe()Anruf zu rechtfertigen . Aber nach dieser WHATWG spec , das XMLHttpRequest()unterliegt die Garbage Collection , sobald es „fertig“ ist, auch wenn es Ereignis Zuhörer daran befestigt. Ich denke, das ist der Grund, warum Angular 2 Official Guide weglässt unsubscribe()und GC die Hörer aufräumen lässt.

Wie für Ihr zweites Beispiel hängt es von der Implementierung von ab params. Ab heute zeigt der eckige offizielle Leitfaden keine Abmeldung mehr von params. Ich habe noch einmal in src nachgesehen und festgestellt, dass dies paramsnur ein BehaviorSubject ist . Da keine Ereignis-Listener oder Timer verwendet wurden und keine globalen Variablen erstellt wurden, sollte das Auslassen sicher sein unsubscribe().

Die Quintessenz Ihrer Frage lautet: Rufen Sie immer unsubscribe()als Schutz vor Speicherverlusten auf, es sei denn, Sie sind sicher, dass bei der Ausführung des Observable keine globalen Variablen erstellt, keine Ereignis-Listener hinzugefügt, keine Timer festgelegt oder andere Aktionen ausgeführt werden, die zu Speicherverlusten führen .

Wenn Sie Zweifel haben, prüfen Sie die Umsetzung dieses Observablen. Wenn das Observable eine Bereinigungslogik in sein Programm geschrieben hat unsubscribe(), die normalerweise vom Konstruktor zurückgegeben wird, haben Sie guten Grund, ernsthaft über einen Aufruf nachzudenken unsubscribe().

Chuanqi Sonne
quelle
6

Die offizielle Dokumentation zu Angular 2 enthält eine Erklärung, wann Sie sich abmelden müssen und wann sie sicher ignoriert werden kann. Schauen Sie sich diesen Link an:

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Suchen Sie nach dem Absatz mit der Überschrift Eltern und Kinder kommunizieren über einen Dienst und dann über das blaue Kästchen:

Beachten Sie, dass wir das Abonnement erfassen und abbestellen, wenn die AstronautComponent zerstört wird. Dies ist ein Speicherleckschutzschritt. In dieser App besteht kein tatsächliches Risiko, da die Lebensdauer einer AstronautComponent der Lebensdauer der App selbst entspricht. Dies wäre in einer komplexeren Anwendung nicht immer der Fall.

Wir fügen diesen Schutz nicht zur MissionControlComponent hinzu, da er als übergeordnetes Element die Lebensdauer des MissionService steuert.

Ich hoffe das hilft dir.

Cerny
quelle
3
Als Komponente weiß man nie, ob man ein Kind ist oder nicht. Daher sollten Sie Abonnements immer als Best Practice abbestellen.
SeriousM
1
Bei MissionControlComponent geht es nicht wirklich darum, ob es sich um ein übergeordnetes Element handelt oder nicht, sondern darum, dass die Komponente selbst den Dienst bereitstellt. Wenn MissionControl zerstört wird, werden auch der Dienst und alle Verweise auf die Instanz des Dienstes zerstört, sodass keine Möglichkeit eines Lecks besteht.
Ender
6

Basierend auf: Verwenden der Klassenvererbung zum Verknüpfen mit dem Angular 2-Komponentenlebenszyklus

Ein weiterer generischer Ansatz:

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
  }

}

Und benutze:

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

Joggen
quelle
1
Dies funktioniert NICHT richtig. Bitte seien Sie vorsichtig, wenn Sie diese Lösung verwenden. Sie verpassen einen this.componentDestroyed$.next()Anruf wie die von Sean oben akzeptierte Lösung ...
Philn
4

Die offizielle Antwort von Edit # 3 (und Variationen) funktioniert gut, aber das, was mich dazu bringt, ist das "Durcheinander" der Geschäftslogik um das beobachtbare Abonnement.

Hier ist ein anderer Ansatz mit Wrappern.

Warnung: experimenteller Code

Die Datei subscribeAndGuard.ts wird verwendet, um eine neue Observable-Erweiterung zum Umschließen .subscribe()und zum Umschließen zu erstellen ngOnDestroy().
Die Verwendung ist dieselbe wie .subscribe()mit Ausnahme eines zusätzlichen ersten Parameters, der auf die Komponente verweist.

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

Hier ist eine Komponente mit zwei Abonnements, eines mit dem Wrapper und eines ohne. Die einzige Einschränkung ist, dass OnDestroy implementiert werden muss (falls gewünscht mit leerem Körper), andernfalls kann Angular die umschlossene Version nicht aufrufen.

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

Ein Demo-Plunker ist da

Ein zusätzlicher Hinweis: Re Edit 3 - Die 'offizielle' Lösung. Dies kann vereinfacht werden, indem takeWhile () anstelle von takeUntil () vor Abonnements verwendet wird und ein einfacher Boolescher Wert anstelle eines anderen Observable in ngOnDestroy verwendet wird.

@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;
  }
}
Richard Matsen
quelle
3

Da die Lösung von seangwright (Edit 3) sehr nützlich zu sein scheint, empfand ich es auch als schwierig, diese Funktion in die Basiskomponente zu packen und anderen Projektteammitgliedern zu empfehlen, super () auf ngOnDestroy aufzurufen, um diese Funktion zu aktivieren.

Diese Antwort bietet eine Möglichkeit, sich von Superaufrufen zu befreien und "componentDestroyed $" zu einem Kern der Basiskomponente zu machen.

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

Und dann können Sie diese Funktion zum Beispiel frei nutzen:

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

}
Val
quelle
3

Nach der Antwort von @seangwright habe ich eine abstrakte Klasse geschrieben, die die Abonnements von "unendlichen" Observablen in Komponenten behandelt:

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

Um es zu verwenden, erweitern Sie es einfach in Ihrer Winkelkomponente und rufen Sie die subscribe()Methode wie folgt auf:

this.subscribe(someObservable, data => doSomething());

Es akzeptiert auch den Fehler und führt Rückrufe wie gewohnt, ein Beobachterobjekt oder gar keine Rückrufe durch. Denken super.ngOnDestroy()Sie daran, aufzurufen, wenn Sie diese Methode auch in der untergeordneten Komponente implementieren.

Hier finden Sie eine zusätzliche Referenz von Ben Lesh: RxJS: Nicht abbestellen .

Mau Muñoz
quelle
2

Ich habe die Lösung von Seangwright ausprobiert (Edit 3).

Dies funktioniert nicht für Observable, das durch Timer oder Intervall erstellt wurde.

Ich habe es jedoch mit einem anderen Ansatz zum Laufen gebracht:

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();
   }
 }
}
Jeff Tham
quelle
2

Ich mag die letzten beiden Antworten, aber ich habe ein Problem , wenn die die Unterklasse verweist "this"inngOnDestroy .

Ich habe es so geändert, und es sieht so aus, als hätte es dieses Problem behoben.

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() {}
}
Scott Williams
quelle
Sie müssen die this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Pfeilfunktion
2

Falls eine Abmeldung erforderlich ist, kann der folgende Operator für die beobachtbare Rohrleitungsmethode verwendet werden

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

es kann so verwendet werden:

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 {
  }
}

Der Operator umschließt die ngOnDestroy-Methode der Komponente.

Wichtig: Der Bediener sollte der letzte in einem beobachtbaren Rohr sein.

Oleg Polezky
quelle
Dies hat großartig funktioniert, aber ein Upgrade auf Winkel 9 scheint es zu töten. Weiß jemand warum?
Ymerej
1

Normalerweise müssen Sie sich abmelden, wenn die Komponenten zerstört werden, aber Angular wird dies im Laufe der Zeit immer mehr tun. In der neuen Nebenversion von Angular4 gibt es diesen Abschnitt zum Weiterleiten des Abmelden:

Müssen Sie sich abmelden?

Wie in ActivatedRoute: Der One-Stop-Shop für Routeninformationen auf der Seite Routing & Navigation beschrieben, verwaltet der Router die von ihm bereitgestellten Observables und lokalisiert die Abonnements. Die Abonnements werden bereinigt, wenn die Komponente zerstört wird, und schützen so vor Speicherlecks. Sie müssen sich also nicht von der Route paramMap Observable abmelden.

Das folgende Beispiel ist auch ein gutes Beispiel aus Angular, um eine Komponente zu erstellen und anschließend zu zerstören. Sehen Sie sich an, wie die Komponente OnDestroy implementiert. Wenn Sie onInit benötigen, können Sie sie auch in Ihrer Komponente implementieren, z. B. implementiert 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();
  }
}
Alireza
quelle
3
Verwirrt. Was sagst du hier? Sie (Angular aktuelle Dokumente / Notizen) scheinen zu sagen, dass Angular sich darum kümmert und später zu bestätigen, dass das Abbestellen ein gutes Muster ist. Vielen Dank.
Jamie
1

Eine weitere kurze Ergänzung zu den oben genannten Situationen ist:

  • Immer abbestellen, wenn neue Werte im abonnierten Stream nicht mehr benötigt werden oder keine Rolle spielen, führt dies in einigen Fällen zu einer weitaus geringeren Anzahl von Triggern und einer Leistungssteigerung. Fälle wie Komponenten, in denen die abonnierten Daten / Ereignisse nicht mehr vorhanden sind oder ein neues Abonnement für einen völlig neuen Stream erforderlich ist (Aktualisierung usw.), sind ein gutes Beispiel für die Abmeldung.
Krishna Ganeriwal
quelle
0

in der SPA-Anwendung bei ngOnDestroy- Funktion (Angular LifeCycle) Für jedes Abonnement müssen Sie sich abmelden . Vorteil => um zu verhindern, dass der Staat zu schwer wird.

Zum Beispiel: in Komponente1:

import {UserService} from './user.service';

private user = {name: 'test', id: 1}

constructor(public userService: UserService) {
    this.userService.onUserChange.next(this.user);
}

im Dienst:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});

in Komponente2:

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();
}
Mojtaba Ramezani
quelle
0

Für die Bearbeitung des Abonnements verwende ich eine "Unsubscriber" -Klasse.

Hier ist die Unsubscriber-Klasse.

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

Und Sie können diese Klasse in jeder Komponente / jedem Service / Effekt usw. verwenden.

Beispiel:

class SampleComponent extends Unsubscriber {
    constructor () {
        super();
    }

    this.addSubscription(subscription);
}
Pratiyush
quelle
0

Sie können die neueste SubscriptionKlasse verwenden, um das Observable mit nicht so unordentlichem Code abzubestellen.

Wir können dies mit tun, normal variableaber es wird override the last subscriptionbei jedem neuen Abonnement so sein, also vermeiden Sie dies, und dieser Ansatz ist sehr nützlich, wenn Sie mit mehr Anzahl von Obseravables und Arten von Obeservables wie BehavoiurSubjectund arbeitenSubject

Abonnement

Stellt eine verfügbare Ressource dar, z. B. die Ausführung eines Observable. Ein Abonnement hat eine wichtige Methode, das Abbestellen, die kein Argument akzeptiert und nur die vom Abonnement gehaltene Ressource entsorgt.

Sie können dies auf zwei Arten verwenden:

  • Sie können das Abonnement direkt an Subscription Array senden

     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());
      }
    
  • mit add()vonSubscription

    subscriptions = new Subscription();
    
    this.subscriptions.add(subscribeOne);
    this.subscriptions.add(subscribeTwo);
    
    ngOnDestroy() {
      this.subscriptions.unsubscribe();
    }
    

A Subscriptionkann untergeordnete Abonnements speichern und alle sicher abbestellen. Diese Methode behandelt mögliche Fehler (z. B. wenn untergeordnete Abonnements null sind).

Hoffe das hilft.. :)

ganesh045
quelle
0

Das SubSink-Paket, eine einfache und konsistente Lösung zum Abbestellen

Wie noch niemand erwähnt hat, möchte ich das von Ward Bell erstellte Subsink-Paket empfehlen: https://github.com/wardbell/subsink#readme .

Ich habe es für ein Projekt verwendet, bei dem wir mehrere Entwickler sind, die es alle verwenden. Es hilft sehr, eine konsistente Arbeitsweise zu haben, die in jeder Situation funktioniert.

SnorreDan
quelle
0

Für Observables, die direkt nach dem Ausgeben des Ergebnisses abgeschlossen werden, wie AsyncSubjectoder zum Beispiel Observables von http-Anfragen und dergleichen, müssen Sie sich nicht abmelden. Es tut nicht weh, nach diesen zu rufen unsubscribe(), aber wenn das Beobachtbare closeddie Abmeldemethode ist, wird einfach nichts getan :

if (this.closed) {
  return;
}

Wenn Sie langlebige Observables haben, die im Laufe der Zeit mehrere Werte ausgeben (wie z. B. a BehaviorSubjectoder a ReplaySubject), müssen Sie sich abmelden, um Speicherverluste zu vermeiden.

Mit einem Pipe-Operator können Sie auf einfache Weise ein Observable erstellen, das direkt nach der Ausgabe eines Ergebnisses aus solch langlebigen Observables abgeschlossen wird. In einigen Antworten wird hier die take(1)Pfeife erwähnt. Aber ich bevorzuge die first()Pfeife . Der Unterschied zu take(1)ist, dass es:

Senden Sie einen EmptyErrorFehlerrückruf an den Beobachter, wenn der Observable abgeschlossen ist, bevor eine nächste Benachrichtigung gesendet wurde.

Ein weiterer Vorteil der ersten Pipe besteht darin, dass Sie ein Prädikat übergeben können, mit dessen Hilfe Sie den ersten Wert zurückgeben können, der bestimmte Kriterien erfüllt:

const predicate = (result: any) => { 
  // check value and return true if it is the result that satisfies your needs
  return true;
}
observable.pipe(first(predicate)).subscribe(observer);

First wird direkt nach der Ausgabe des ersten Werts abgeschlossen (oder beim Übergeben eines Funktionsarguments den ersten Wert, der Ihrem Prädikat entspricht), sodass Sie sich nicht abmelden müssen.

Manchmal sind Sie sich nicht sicher, ob Sie eine langlebige Beobachtbarkeit haben oder nicht. Ich sage nicht, dass es eine gute Praxis ist, aber Sie könnten dann immer die hinzufügenfirst Pipe , um sicherzustellen, dass Sie sich nicht manuell abmelden müssen. Das Hinzufügen einer zusätzlichen firstPipe zu einem Observable, das nur einen Wert ausgibt, schadet nicht.

Während der Entwicklung können Sie das singleRohr verwenden , die fehlschlägt, wenn die beobachtbare Quelle mehrere Ereignisse ausgibt. Dies kann Ihnen helfen, die Art der beobachtbaren Daten zu untersuchen und festzustellen, ob eine Abmeldung erforderlich ist oder nicht.

observable.pipe(single()).subscribe(observer);

Die firstund singlescheinen sehr ähnlich zu sein, beide Pipes können ein optionales Prädikat annehmen, aber die Unterschiede sind wichtig und werden in dieser Stackoverflow-Antwort hier gut zusammengefasst :

Zuerst

Wird ausgegeben, sobald das erste Element angezeigt wird. Wird gleich danach abgeschlossen.

Single

Schlägt fehl, wenn die beobachtbare Quelle mehrere Ereignisse ausgibt.


Hinweis Ich habe versucht, in meiner Antwort mit Verweisen auf die offizielle Dokumentation so genau und vollständig wie möglich zu sein. Bitte kommentieren Sie, wenn etwas Wichtiges fehlt ...

Verwelken
quelle
-1

--- Aktualisieren Sie Angular 9 und Rxjs 6 Solution

  1. Verwendung unsubscribeim ngDestroyLebenszyklus der Winkelkomponente
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$.subscribe( ... );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. Verwendung takeUntilin Rxjs
class SampleComponent implements OnInit, OnDestroy {
  private unsubscribe$: new Subject<void>;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe( ... );
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
  1. Für einige Aktionen, die Sie dabei aufrufen ngOnInit, geschieht dies nur einmal, wenn die Komponente initialisiert wird.
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(take(1))
    .subscribe( ... );
  }
}

Wir haben auch asyncPfeife. Diese wird jedoch für die Vorlage verwendet (nicht in der Winkelkomponente).

Hoang Tran Sohn
quelle
Ihr erstes Beispiel ist unvollständig.
Paul-Sebastian Manole