wake-up-neo.com

Angular / RxJs Wann sollte ich mich vom Abonnement abmelden?

Wann sollte ich die Subscription -Instanzen 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 Guide Abonnements wie folgt ignorieren:

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

Gleichzeitig sagt Route & Navigation Guide Folgendes:

Irgendwann navigieren wir woanders hin. 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 haben unser Observable in der ngOnDestroy -Methode abbestellt.

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

--- Edit 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 soll. Die Diskussion beginnt um ca. 1:05:30 Uhr.

Ward erwähnt right now there's an awful takeUntil dance that takes a lot of machinery und 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, um Observables das Einbinden in die Angular Komponenten-Lebenszyklusereignisse zu ermöglichen, und Ward schlägt eine Observable von Lebenszyklusereignissen vor, die eine Komponente abonnieren kann, um zu wissen, wann Observables abgeschlossen werden müssen als komponenteninterner Zustand gepflegt.

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 zur Durchsetzung. https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

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

  3. Eine weitere Abwandlung 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 der gesamten 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 übermäßige Verwendung von takeUntil() ein Zeichen dafür sein könnte, dass Ihre Komponente versucht, zu viel zu tun, und dass Sie Ihre vorhandenen Komponenten in Feature und Präsentationskomponenten sollten berücksichtigt werden. Sie können dann das Observable aus der Feature-Komponente in ein Input der Presentational-Komponente | async, 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 auf der NGConf gesprochen (ich habe ihm sogar diese Antwort gezeigt, von der er sagte, dass sie korrekt ist), aber er hat mir gesagt, dass das Doc-Team für Angular eine Lösung für diese Frage hat, die nicht veröffentlicht ist (obwohl sie nicht veröffentlicht ist) arbeiten daran, es zu genehmigen). Er sagte mir auch, dass ich meine SO Antwort mit der bevorstehenden offiziellen Empfehlung aktualisieren könnte.

Die Lösung, die wir in Zukunft alle verwenden sollten, besteht darin, ein private ngUnsubscribe = new Subject(); -Feld zu allen Komponenten hinzuzufügen, deren Klassencode .subscribe() -Aufrufe für Observables enthält.

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

Die geheime Sauce (wie bereits von @ metamaker angegeben) besteht darin, takeUntil(this.ngUnsubscribe) vor jedem unserer .subscribe() Aufrufe aufzurufen, wodurch sichergestellt wird, 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 Operator takeUntil als letzten Operator hinzuzufügen, um Undichtigkeiten mit dazwischen liegenden Observablen in der Operatorkette zu vermeiden.

--- Edit 2 (28.12.2016)

Quelle 5

Das Tutorial Angular im Kapitel Routing enthält nun folgende Informationen: "Der Router verwaltet die von ihm bereitgestellten Observables und lokalisiert die Subskriptionen. Die Subskriptionen werden bereinigt, wenn die Komponente zerstört wird, um vor Speicherlecks zu schützen müssen sich nicht von den Routenparametern abmelden Observable. " - Mark Rajcok

Hier ist ein Diskussion zu den Github-Themen für die Angular -Dokumente zu Router Observables, in denen Ward Bell erwähnt, dass eine Klärung für all dies in Vorbereitung ist.

--- Bearbeiten 1

Quelle 4

In diesem Video von NgEurope sagt Rob Wormald auch, dass Sie sich nicht von Router Observables abmelden müssen. Er erwähnt auch den http-Dienst und ActivatedRoute.params in diesem Video vom November 2016 .

--- Ursprüngliche Antwort

TLDR:

Für diese Frage gibt es (2) Arten von Observables - endlichen Werten und unendlichen Wert.

httpObservables produzieren endliche (1) Werte und so etwas wie ein DOM event listenerObservables produzieren unendlich Werte.

Wenn Sie subscribe manuell aufrufen (keine asynchrone Pipe verwenden), dann unsubscribe von unendlich Observables.

Mach dir keine Sorgen um endliche , RxJs wird sich um sie kümmern.

Quelle 1

Ich habe eine Antwort von Rob Wormald in Angulars Gitter aufgespürt hier .

Er stellt fest (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 melden sich manuell im Controller an)

ich sollte sagen, "wenn es eine Sequenz ist, die vervollständigt" (von denen Einzelwertsequenzen, a la http, eine sind)

Wenn es sich um eine unendliche Folge handelt , sollten Sie das Abonnement aufheben, das die asynchrone Pipe ausführt für dich

Außerdem erwähnt er in diesem Youtube-Video on Observables, dass they clean up after themselves... im Kontext von Observables, die complete (wie Versprechen, die immer vervollständigen, weil sie immer 1 Wert produzieren und enden - wir Ich habe mir nie Sorgen gemacht, dass ich mich von Promises abmelden muss, um sicherzustellen, dass xhr Event-Listener bereinigt werden, oder?).

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 Observable hat eine längere Lebensdauer als unser Abonnement. Das Standardverhalten von Observable-Operatoren besteht darin, das Abonnement zu löschen, sobald .complete () - oder .error () -Meldungen veröffentlicht werden. Denken Sie daran, dass RxJS so konzipiert wurde, dass es die meiste Zeit "Feuer und Vergessen" funktioniert.

Wann gilt der Ausdruck 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 Abschluss von Observable zerstört wurde.

Ich lese dies als Bedeutung, wenn wir eine http-Anforderung oder eine Observable abonnieren, die 10 Werte ausgibt, und unsere Komponente zerstört wird, bevor diese http-Anforderung zurückgegeben wird oder die 10 Werte ausgegeben wurden. Wir sind immer noch in Ordnung!

Wenn die Anforderung zurückgegeben wird oder der 10. Wert endgültig ausgegeben wird, wird Observable abgeschlossen und alle Ressourcen werden bereinigt.

Quelle 3

Wenn wir uns dieses Beispiel aus der gleichen Rangle-Anleitung ansehen, können wir sehen, dass das Subscription to route.params ein unsubscribe() erfordert, da wir nicht wissen, wann diese params aufhören werden, sich zu ändern (neue Werte aussenden).

Die Komponente kann durch Wegnavigation zerstört werden. In diesem Fall ändern sich die Routenparameter wahrscheinlich noch (sie können sich technisch ändern, bis die App endet), und die im Abonnement zugewiesenen Ressourcen werden weiterhin zugewiesen, da es keine completion gab.

898
seangwright

Sie müssen nicht viele Abonnements haben und das Abonnement manuell kündigen. Verwenden Sie Betreff und takeUntil , um Abonnements wie ein Boss 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()
  }
}

Alternativer Ansatz , der vorgeschlagen wurde von @acumartini in Kommentaren , verwendet takeWhile anstelle von - takeUntil . Sie mögen es vorziehen, aber beachten Sie, 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). Methode, die auf takeUntil basiert, hat diesen Nachteil nicht und führt zum sofortigen Abbruch der Anfrage. Danke 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
  }
}
75
metamaker

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 Abonnement zu kündigen, die kein Argument akzeptiert und nur die im Abonnement enthaltene Ressource entsorgt.
Außerdem können Abonnements mit der Methode add () zusammengefasst werden, mit der ein untergeordnetes Abonnement an das aktuelle Abonnement angehängt wird. Wenn ein Abonnement nicht abonniert wird, werden alle seine untergeordneten Elemente (und seine Enkelkinder) zusammengefasst auch abgemeldet.

Sie können ein aggregiertes Abonnementobjekt erstellen, das alle Ihre Abonnements gruppiert. Dazu erstellen Sie ein leeres Abonnement und fügen es mit der add() -Methode hinzu. Wenn Ihre Komponente zerstört ist, müssen Sie nur das Aggregatabonnement 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();
  }
}
60
Steven Liekens

Einige der Best Practices bezüglich der Aufhebung der Abonnements für Observables in Angular - Komponenten:

Ein Zitat aus Routing & Navigation

Wenn Sie ein Observable in einer Komponente abonnieren, können Sie das Abonnement fast immer kündigen, wenn die Komponente zerstört wird.

Es gibt einige außergewöhnliche Observablen, 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 ab.

Sie können sich trotzdem abmelden. Es ist harmlos und niemals eine schlechte Praxis.

Und als Antwort auf die folgenden Links:

Ich habe einige der Best Practices bezüglich der Abmeldung von Observablen in Angular - Komponenten zusammengestellt, um sie mit Ihnen zu teilen:

  • http observable unsubscription ist bedingt und wir sollten die Auswirkungen des 'subscribe callback' berücksichtigen, der ausgeführt wird, nachdem die Komponente von Fall zu Fall zerstört wurde. Wir wissen, dass angular die beobachtbare Variable http abbestellt und bereinigt (1) , (2) . Während dies aus Sicht der Ressourcen wahr ist, erzählt es nur die halbe Geschichte. Angenommen, wir sprechen über das direkte Aufrufen von http aus einer Komponente heraus, und die Antwort von http hat länger als erforderlich gedauert, sodass der Benutzer die Komponente geschlossen hat. Der Handler subscribe() wird weiterhin aufgerufen, auch wenn die Komponente geschlossen und zerstört wird. Dies kann unerwünschte Nebenwirkungen haben und im schlimmsten Fall dazu führen, dass der Anwendungsstatus unterbrochen wird. 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. Angenommen, Sie erstellen einen E-Mail-Client und lösen einen Signalton aus, wenn die E-Mail gesendet wurde. Nun, Sie möchten, dass dies auch dann geschieht, wenn die Komponente geschlossen wird. ( 8 ).
  • Sie müssen sich nicht von vollständigen oder fehlerhaften Observablen abmelden. Dies schadet jedoch nicht (7) .
  • Verwenden Sie AsyncPipe so oft wie möglich, da es sich automatisch vom Observable bei Zerstörung von Komponenten abmeldet.
  • Kündigen Sie das Abonnement für die Observablen ActivatedRoute wie _route.params_, wenn sie in einer verschachtelten (in tpl mit der Komponentenauswahl hinzugefügten) oder dynamischen Komponente abonniert sind, da sie möglicherweise so oft abonniert werden, wie die übergeordnete/Host-Komponente vorhanden ist. In anderen Szenarien müssen Sie sich nicht abmelden, wie im obigen Zitat aus Routing & Navigation -Dokumenten erwähnt.
  • Kündigen Sie das Abonnement für globale Observables, die von Komponenten gemeinsam genutzt werden, die über einen Angular - Dienst verfügbar gemacht werden, da sie möglicherweise mehrmals abonniert werden, solange die Komponente initialisiert wird.
  • Das Abbestellen interner Observables eines anwendungsspezifischen Dienstes ist nicht erforderlich, da dieser Dienst niemals zerstört wird, es sei denn, Ihre gesamte Anwendung wird zerstört. Es gibt keinen wirklichen Grund, das Abonnement abzubrechen, und es besteht keine Gefahr von Speicherverlusten. (6) .

    Hinweis: In Bezug auf bereichsspezifische Dienste, d. H. Komponentenanbieter, werden sie zerstört, wenn die Komponente zerstört wird. In diesem Fall sollten wir, wenn wir Observable innerhalb dieses Providers abonnieren, in Betracht ziehen, das Abonnement mit dem OnDestroy -Lebenszyklus-Hook zu kündigen, der laut Dokumentation aufgerufen wird, wenn der Service zerstört wird.
  • Verwenden Sie eine abstrakte Technik, um Codeverwirrungen zu vermeiden, die durch Abbestellungen entstehen können. Sie können Ihre Abonnements mit takeUntil(3) verwalten, oder Sie können dieses npm-Paket verwenden, das unter (4) erwähnt wird Observables in Angular abbestellen.
  • Immer abbestellen von FormGroup observables wie _form.valueChanges_ und _form.statusChanges_
  • Immer abbestellen von Observables von _Renderer2_ Service wie _renderer2.listen_
  • Kündigen Sie das Abonnement von allen anderen beobachtbaren Elementen als Speicherverlust-Schutzschritt, bis in Angular Docs explizit angegeben wird, welche beobachtbaren Elemente nicht abgemeldet werden müssen (Problem überprüfen: (5) Dokumentation für das Abmelden von RxJS (Offen) ).
  • Bonus: Verwenden Sie immer die Angular - Methoden, um Ereignisse wie HostListener zu binden, da angular dafür sorgt, dass die Ereignis-Listener bei Bedarf entfernt werden und ein möglicher Speicherverlust aufgrund von Ereignisbindungen verhindert wird.

Ein guter letzter Tipp : Wenn Sie nicht wissen, ob ein Observable automatisch abgemeldet wird oder nicht, fügen Sie complete einen Rückruf zu subscribe(...) hinzu und prüfen Sie, ob Es wird aufgerufen, wenn die Komponente zerstört wird.

26
Mouneer

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

Sehen wir uns Ihre Beispiele genauer an:

getHero() gibt das Ergebnis von http.get() zurück. Wenn Sie in den angular 2 Quellcode schauen, erstellt http.get() zwei Ereignis-Listener:

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

und indem Sie unsubscribe() aufrufen, können Sie die Anfrage sowie die Listener abbrechen:

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

Beachten Sie, dass _xhr plattformspezifisch ist, aber ich denke, es kann davon ausgegangen werden, dass es sich in Ihrem Fall um eine XMLHttpRequest() handelt.

Normalerweise reicht dies aus, um einen manuellen Aufruf von unsubscribe() zu rechtfertigen. Aber gemäß dieser WHATWG-Spezifikation unterliegt die XMLHttpRequest() der Garbage Collection, sobald sie "erledigt" ist, auch wenn daran Ereignis-Listener angehängt sind. Ich denke, deshalb lässt angular 2 die offizielle Anleitung unsubscribe() aus und lässt GC die Zuhörer aufräumen.

Das zweite Beispiel hängt von der Implementierung von params ab. Ab heute wird im angular offiziellen Leitfaden nicht mehr die Abmeldung von params angezeigt. Ich habe noch einmal in src nachgesehen und festgestellt, dass params nur ein BehaviorSubject ist. Da keine Ereignis-Listener oder Timer verwendet wurden und keine globalen Variablen erstellt wurden, sollte es sicher sein, unsubscribe() wegzulassen.

Die Quintessenz Ihrer Frage ist, dass Sie unsubscribe() immer als Schutz vor Speicherverlusten aufrufen, es sei denn, Sie sind sicher, dass die Ausführung der Observable keine globalen Variablen erstellt, Ereignis-Listener hinzufügt, Timer festlegt oder etwas anderes tut das führt zu Speicherlecks.

Im Zweifelsfall sollten Sie die Implementierung dieser Beobachtungsgröße untersuchen. Wenn das Observable eine Bereinigungslogik in sein unsubscribe() geschrieben hat, was normalerweise die vom Konstruktor zurückgegebene Funktion ist, dann haben Sie guten Grund, ernsthaft darüber nachzudenken, unsubscribe() aufzurufen.

15
evenstar

In der offiziellen Dokumentation zu Angular 2 wird erläutert, wann Sie sich abmelden müssen und wann Sie sie ignorieren können. 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 das blaue Kästchen:

Beachten Sie, dass wir das Abonnement erfassen und abbestellen, wenn die AstronautComponent zerstört wird. Dies ist ein Speicherverlustschutzschritt. Diese App birgt 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.

Dieser Schutz wird der MissionControlComponent nicht hinzugefügt, da er als übergeordnetes Element die Lebensdauer des MissionService steuert.

Ich hoffe das hilft dir.

6
Cerny

Basierend auf: Verwenden der Klassenvererbung zum Verknüpfen mit 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));
    }
}
5
JoG

Da die Lösung von seangwright (Edit 3) sehr nützlich zu sein scheint, empfand ich es auch als mühsam, diese Funktion in eine Basiskomponente zu packen, und gab anderen Projektmitarbeitern den Hinweis, dass sie beim Aktivieren dieser Funktion super () auf ngOnDestroy aufrufen sollten.

Diese Antwort bietet eine Möglichkeit, sich vom Super-Aufruf 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 frei verwenden, zum Beispiel:

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

}
3
Val

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

Hier ist ein weiterer Ansatz mit Wrappern.

Warnen: experimenteller Code

Die Datei subscribeAndGuard.ts wird verwendet, um eine neue Observable-Erweiterung zu erstellen, in die .subscribe() und in die ngOnDestroy() eingeschlossen wird.
Die Verwendung entspricht der von .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 Subskriptionen, eine mit dem Wrapper und eine ohne. Die einzige Einschränkung ist, dass OnDestroy implementiert werden muss (bei Bedarf mit leerem Body), da Angular die umschlossene Version sonst nicht aufrufen kann .

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 hier

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

@Component({...})
export class SubscribingComponent implements OnInit, OnDestroy {

  iAmAlive = true;
  ngOnInit() {

    Observable.interval(1000)
      .takeWhile(() => { return this.iAmAlive; })
      .subscribe((data) => { console.log(data); });
  }

  ngOnDestroy() {
    this.iAmAlive = false;
  }
}
3
Richard Matsen

Nach der Antwort von @ seangwright habe ich eine abstrakte Klasse geschrieben, die "unendliche" Observables-Subskriptionen 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 angular Komponente und rufen Sie die subscribe() Methode wie folgt auf:

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

Es akzeptiert auch den Fehler und schließt Rückrufe wie gewohnt ab, ein Beobachterobjekt oder überhaupt keine Rückrufe. Denken Sie daran, super.ngOnDestroy() aufzurufen, wenn Sie diese Methode auch in der untergeordneten Komponente implementieren.

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

3
Mau Muñoz

Ich mag die letzten beiden Antworten, aber ich habe ein Problem festgestellt, wenn die Unterklasse in ngOnDestroy auf "this" verwiesen hat.

Ich habe es so geändert, dass es das Problem behebt.

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

Ich habe seangwrights lösung ausprobiert (edit 3)

Das funktioniert nicht für Observable, die durch Timer oder Intervall erstellt wurden.

Allerdings habe ich es funktioniert mit einem anderen Ansatz:

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

Normalerweise müssen Sie das Abonnement kündigen, wenn die Komponenten zerstört werden, aber Angular wird es im Laufe der Zeit mehr und mehr verarbeiten. In der neuen Nebenversion von Angular4 finden Sie in diesem Abschnitt Informationen zum Kündigen des Abonnements:

Müssen Sie sich abmelden?

Wie im Abschnitt ActivatedRoute: 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. Dies schützt vor Speicherverlusten, sodass Sie das Abonnement für die Route paramMap Observable nicht kündigen müssen.

Das folgende Beispiel ist auch ein gutes Beispiel von 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 es 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();
  }
}
_
1
Alireza

Für den Fall, dass das Abbestellen erforderlich ist, kann der folgende Operator für die Observable-Pipe-Methode 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 bricht die Methode ngOnDestroy der Komponente ab.

Wichtig: Der Bediener sollte der letzte in der beobachtbaren Leitung sein.

1
Oleg Polezky

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 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 vollständig neuen Stream erforderlich ist (Aktualisierung usw.), sind ein gutes Beispiel für die Abmeldung.
1
kg11

in der SPA-Anwendung bei ngOnDestroy Funktion (Angular LifeCycle) Für jedes Abonnement müssen Sie Abbestellen es. 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 Komponente 2:

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

Für die Abwicklung 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/Service/Effekt usw. verwenden.

Beispiel:

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

    this.addSubscription(subscription);
}
0
Pratiyush