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 derngOnDestroy
-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();
}
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.
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
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
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
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
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 Observable
s 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.
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.
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 .
TLDR:
Für diese Frage gibt es (2) Arten von Observables
- endlichen Werten und unendlichen Wert.
http
Observables
produzieren endliche (1) Werte und so etwas wie ein DOM event listener
Observables
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.
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
}
}
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();
}
}
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:
http
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 ).AsyncPipe
so oft wie möglich, da es sich automatisch vom Observable bei Zerstörung von Komponenten abmeldet.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.OnDestroy
-Lebenszyklus-Hook zu kündigen, der laut Dokumentation aufgerufen wird, wenn der Service zerstört wird.takeUntil
(3) verwalten, oder Sie können dieses npm
-Paket verwenden, das unter (4) erwähnt wird Observables in Angular abbestellen.FormGroup
observables wie _form.valueChanges
_ und _form.statusChanges
_Renderer2
_ Service wie _renderer2.listen
_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.
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.
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.
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));
}
}
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');
}
}
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;
}
}
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 .
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() {}
}
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();
}
}
}
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();
}
}
_
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.
Eine weitere kurze Ergänzung zu den oben genannten Situationen ist:
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();
}
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);
}