本文翻译自:Angular/RxJs When should I unsubscribe from `Subscription`
When should I store the Subscription
instances and invoke unsubscribe()
during the NgOnDestroy life cycle and when can I simply ignore them? 在NgOnDestroy生命周期中,什么时候应该存储Subscription
实例并调用unsubscribe()
?什么时候可以忽略它们?
Saving all subscriptions introduces a lot of mess into component code. 保存所有订阅会在组件代码中带来很多麻烦。
HTTP Client Guide ignore subscriptions like this: HTTP客户端指南会忽略这样的订阅:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
In the same time Route & Navigation Guide says that: 同时,《 路线与导航指南 》指出:
Eventually, we'll navigate somewhere else. 最终,我们将导航到其他地方。 The router will remove this component from the DOM and destroy it. 路由器将从DOM中删除此组件并销毁它。 We need to clean up after ourselves before that happens. 我们需要在此之前进行自我清理。 Specifically, we must unsubscribe before Angular destroys the component. 具体来说,我们必须在Angular销毁组件之前取消订阅。 Failure to do so could create a memory leak. 否则可能会导致内存泄漏。
We unsubscribe from our
Observable
in thengOnDestroy
method. 我们通过ngOnDestroy
方法取消订阅Observable
。
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();
}
#1楼
参考:https://stackoom.com/question/2ZThe/Angular-RxJs我应该何时退订-Subscription
#2楼
Angular 2 official documentation provides an explanation for when to unsubscribe and when it can be safely ignored. Angular 2官方文档提供了有关何时退订以及何时可以安全忽略的说明。 Have a look at this link: 看一下这个链接:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Look for the paragraph with the heading Parent and children communicate via a service and then the blue box: 查找标题为“ 父母和子女通过服务进行交流”的段落,然后显示蓝色框:
Notice that we capture the subscription and unsubscribe when the AstronautComponent is destroyed. 请注意,当AstronautComponent被销毁时,我们捕获了订阅并取消订阅。 This is a memory-leak guard step. 这是内存泄漏保护措施。 There is no actual risk in this app because the lifetime of a AstronautComponent is the same as the lifetime of the app itself. 此应用程序没有实际风险,因为AstronautComponent的寿命与应用程序本身的寿命相同。 That would not always be true in a more complex application. 在更复杂的应用程序中,并非总是如此。
We do not add this guard to the MissionControlComponent because, as the parent, it controls the lifetime of the MissionService. 我们不将此防护添加到MissionControlComponent中,因为它作为父级控制着MissionService的生命周期。
I hope this helps you. 我希望这可以帮助你。
#3楼
It depends. 这取决于。 If by calling someObservable.subscribe()
, you start holding up some resource that must be manually freed-up when the lifecycle of your component is over, then you should call theSubscription.unsubscribe()
to prevent memory leak. 如果通过调用someObservable.subscribe()
开始保存一些资源,这些资源在组件的生命周期结束时必须手动释放,则应调用theSubscription.unsubscribe()
以防止内存泄漏。
Let's take a closer look at your examples: 让我们仔细看看您的示例:
getHero()
returns the result of http.get()
. getHero()
返回的结果http.get()
If you look into the angular 2 source code , http.get()
creates two event listeners: 如果查看angular 2 源代码 ,则http.get()
创建两个事件侦听器:
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
and by calling unsubscribe()
, you can cancel the request as well as the listeners: 通过调用unsubscribe()
,您可以取消请求以及侦听器:
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
Note that _xhr
is platform specific but I think it's safe to assume that it is an XMLHttpRequest()
in your case. 请注意, _xhr
是特定_xhr
平台的,但是我认为可以安全地假定它是XMLHttpRequest()
。
Normally, this is enough evidence to warrant a manual unsubscribe()
call. 通常,这足以保证手动进行unsubscribe()
调用。 But according this WHATWG spec , the XMLHttpRequest()
is subject to garbage collection once it is "done", even if there are event listeners attached to it. 但是根据此WHATWG规范 , XMLHttpRequest()
一旦“完成”,就将进行垃圾回收,即使已附加事件侦听器也是如此。 So I guess that's why angular 2 official guide omits unsubscribe()
and lets GC clean up the listeners. 所以我想这就是为什么angular 2官方指南省略了unsubscribe()
并让GC清理监听器的原因。
As for your second example, it depends on the implementation of params
. 至于第二个示例,它取决于params
的实现。 As of today, the angular official guide no longer shows unsubscribing from params
. 从今天起,有角度的官方指南不再显示对params
取消订阅。 I looked into src again and found that params
is a just a BehaviorSubject . 我再次查看了src ,发现params
只是一个BehaviorSubject 。 Since no event listeners or timers were used, and no global variables were created, it should be safe to omit unsubscribe()
. 由于没有使用事件侦听器或计时器,也没有创建全局变量,因此省略unsubscribe()
应该是安全的。
The bottom line to your question is that always call unsubscribe()
as a guard against memory leak, unless you are certain that the execution of the observable doesn't create global variables, add event listeners, set timers, or do anything else that results in memory leaks. 您问题的底线是,始终调用unsubscribe()
来防止内存泄漏,除非您确定observable的执行不会创建全局变量,添加事件侦听器,设置计时器或执行任何其他导致结果的操作在内存泄漏。
When in doubt, look into the implementation of that observable. 如有疑问,请查看该可观察的实现。 If the observable has written some clean up logic into its unsubscribe()
, which is usually the function that is returned by the constructor, then you have good reason to seriously consider calling unsubscribe()
. 如果可观察对象已在其unsubscribe()
编写了一些清理逻辑,通常是构造函数返回的函数,则您有充分的理由认真考虑调用unsubscribe()
。
#4楼
--- Edit 4 - Additional Resources (2018/09/01) -编辑4-其他资源(2018/09/01)
In a recent episode of Adventures in Angular Ben Lesh and Ward Bell discuss the issues around how/when to unsubscribe in a component. 在最近的《 Angular冒险》中, Ben Lesh和Ward Bell讨论了如何/何时取消订阅组件中的问题。 The discussion starts at about 1:05:30. 讨论从大约1:05:30开始。
Ward mentions right now there's an awful takeUntil dance that takes a lot of machinery
and Shai Reznik mentions Angular handles some of the subscriptions like http and routing
. 沃德(Ward) right now there's an awful takeUntil dance that takes a lot of machinery
提到right now there's an awful takeUntil dance that takes a lot of machinery
而Shai Reznik则提到Angular handles some of the subscriptions like http and routing
。
In response Ben mentions that there are discussions right now to allow Observables to hook into the Angular component lifecycle events and Ward suggests an Observable of lifecycle events that a component could subscribe to as a way of knowing when to complete Observables maintained as component internal state. 作为回应,Ben提到,现在正在进行讨论,以允许Observables参与Angular组件的生命周期事件,Ward建议组件可以订阅的Observable生命周期事件,以了解何时完成以组件内部状态维护的Observables。
That said, we mostly need solutions now so here are some other resources. 就是说,我们现在最需要解决方案,因此这里有一些其他资源。
A recommendation for the
takeUntil()
pattern from RxJs core team member Nicholas Jamieson and a tslint rule to help enforce it. 来自RxJs核心团队成员Nicholas Jamieson的takeUntil()
模式的建议,以及一条有助于实施的tslint规则。 https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047efLightweight npm package that exposes an Observable operator that takes a component instance (
this
) as a parameter and automatically unsubscribes duringngOnDestroy
. 轻量级npm软件包,它公开一个Observable运算符,该运算符将组件实例(this
)作为参数,并在ngOnDestroy
期间自动取消订阅。 https://github.com/NetanelBasal/ngx-take-until-destroy https://github.com/NetanelBasal/ngx-take-until-destroyAnother variation of the above with slightly better ergonomics if you are not doing AOT builds (but we should all be doing AOT now). 如果您不进行AOT构建,则上述方法的另一个变化是人体工程学要好一些(但我们现在都应该进行AOT)。 https://github.com/smnbbrv/ngx-rx-collector https://github.com/smnbbrv/ngx-rx-collector
Custom directive
*ngSubscribe
that works like async pipe but creates an embedded view in your template so you can refer to the 'unwrapped' value throughout your template. 自定义指令*ngSubscribe
工作方式类似于异步管道,但在模板中创建了嵌入式视图,因此您可以在整个模板中引用“ unwrapped”值。 https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f
I mention in a comment to Nicholas' blog that over-use of takeUntil()
could be a sign that your component is trying to do too much and that separating your existing components into Feature and Presentational components should be considered. 我在Nicholas博客的评论中提到,过度使用takeUntil()
可能表明您的组件正在尝试做太多事情,应该考虑将现有组件分为Feature和Presentational组件。 You can then | async
然后,您可以| async
| async
the Observable from the Feature component into an Input
of the Presentational component, which means no subscriptions are necessary anywhere. 将Observable从Feature组件| async
到Presentational组件的Input
中,这意味着在任何地方都不需要订阅。 Read more about this approach here 在此处阅读有关此方法的更多信息
--- Edit 3 - The 'Official' Solution (2017/04/09) -编辑3-``官方''解决方案(2017/04/09)
I spoke with Ward Bell about this question at NGConf (I even showed him this answer which he said was correct) but he told me the docs team for Angular had a solution to this question that is unpublished (though they are working on getting it approved). 我在NGConf上与Ward Bell讨论了这个问题(我什至向他展示了这个答案,他说的是正确的),但他告诉我Angular的文档小组对这个问题尚未解决(尽管他们正在努力使它得到批准) )。 He also told me I could update my SO answer with the forthcoming official recommendation. 他还告诉我,我可以通过即将提出的官方建议来更新我的SO答案。
The solution we should all use going forward is to add a private ngUnsubscribe = new Subject();
今后我们应该使用的解决方案是添加一个private ngUnsubscribe = new Subject();
field to all components that have .subscribe()
calls to Observable
s within their class code. 所有在其类代码中具有.subscribe()
调用Observable
的组件的字段。
We then call this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
然后,我们将其称为this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
in our ngOnDestroy()
methods. 在我们的ngOnDestroy()
方法中。
The secret sauce (as noted already by @metamaker ) is to call takeUntil(this.ngUnsubscribe)
before each of our .subscribe()
calls which will guarantee all subscriptions will be cleaned up when the component is destroyed. 秘密之处(如@metamaker所述 )是在我们每个.subscribe()
调用之前调用takeUntil(this.ngUnsubscribe)
,这将确保在销毁组件时清除所有订阅。
Example: 例:
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();
}
}
Note: It's important to add the takeUntil
operator as the last one to prevent leaks with intermediate observables in the operator chain. 注意:重要的是,将takeUntil
运算符添加为最后一个,以防止运算符链中的中间可观察对象泄漏。
--- Edit 2 (2016/12/28) -编辑2(2016/12/28)
Source 5 来源5
The Angular tutorial, the Routing chapter now states the following: "The Router manages the observables it provides and localizes the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against memory leaks, so we don't need to unsubscribe from the route params Observable." Angular教程的“路由”一章现在指出以下内容:“路由器管理它提供的可观察对象并本地化订阅。在销毁组件时清理订阅,以防止内存泄漏,因此我们无需取消订阅路线参数可观察到。” - Mark Rajcok -Mark Rajcok
Here's a discussion on the Github issues for the Angular docs regarding Router Observables where Ward Bell mentions that clarification for all of this is in the works. 这是针对有关Router Observables的Angular文档的Github问题的讨论 ,Ward Bell提到正在为所有这些问题进行澄清。
--- Edit 1 -编辑1
Source 4 来源4
In this video from NgEurope Rob Wormald also says you do not need to unsubscribe from Router Observables. 在NgEurope的这段视频中, Rob Wormald还说您不需要取消订阅Router Observables。 He also mentions the http
service and ActivatedRoute.params
in this video from November 2016 . 他还从2016年11月开始在此视频中提到了http
服务和ActivatedRoute.params
。
--- Original Answer ---原始答案
TLDR: TLDR:
For this question there are (2) kinds of Observables
- finite value and infinite value. 对于此问题,有(2)种Observables
值- 有限值和无限值。
http
Observables
produce finite (1) values and something like a DOM event listener
Observables
produce infinite values. http
Observables
产生有限 (1)值,类似DOM event listener
东西Observables
产生无限值。
If you manually call subscribe
(not using async pipe), then unsubscribe
from infinite Observables
. 如果您手动调用subscribe
(不使用异步管道),则unsubscribe
无限的 Observables
。
Don't worry about finite ones, RxJs
will take care of them. 不必担心有限的RxJs
会照顾他们。
Source 1 来源1
I tracked down an answer from Rob Wormald in Angular's Gitter here . 我在这里从Angular的Gitter中找到了 Rob Wormald的答案。
He states (i reorganized for clarity and emphasis is mine) 他指出(为清晰起见,我进行了重组,重点是我的)
if its a single-value-sequence (like an http request) the manual cleanup is unnecessary (assuming you subscribe in the controller manually) 如果它是单值序列 (例如http请求), 则不需要手动清理 (假设您手动订阅了控制器)
i should say "if its a sequence that completes " (of which single value sequences, a la http, are one) 我应该说“如果它是一个完成的序列 ”(其中一个单值序列,例如la http,是一个)
if its an infinite sequence , you should unsubscribe which the async pipe does for you 如果它是无限序列 , 则应退订异步管道为您执行的操作
Also he mentions in this youtube video on Observables that they clean up after themselves
... in the context of Observables that complete
(like Promises, which always complete because they are always producing 1 value and ending - we never worried about unsubscribing from Promises to make sure they clean up xhr
event listeners, right?). 他还在YouTube上有关Observables的视频中提到, they clean up after themselves
……在complete
的Observables的背景下(例如Promises,由于它们始终产生1值并结束,因此它们总是完成-我们从不担心从Promises退订到确保他们清理了xhr
事件监听器,对吗?)。
Source 2 来源2
Also in the Rangle guide to Angular 2 it reads 同样在Angular 2的Rangle指南中
In most cases we will not need to explicitly call the unsubscribe method unless we want to cancel early or our Observable has a longer lifespan than our subscription. 在大多数情况下,除非我们想提早取消或Observable的寿命比订阅的寿命长,否则我们无需显式调用unsubscribe方法。 The default behavior of Observable operators is to dispose of the subscription as soon as .complete() or .error() messages are published. Observable运算符的默认行为是在发布.complete()或.error()消息后立即处理订阅。 Keep in mind that RxJS was designed to be used in a "fire and forget" fashion most of the time. 请记住,RxJS被设计为大多数时候以“即弃即用”的方式使用。
When does the phrase our Observable has a longer lifespan than our subscription
apply? 什么时候our Observable has a longer lifespan than our subscription
短语our Observable has a longer lifespan than our subscription
?
It applies when a subscription is created inside a component which is destroyed before (or not 'long' before) the Observable
completes. 它适用于在组件内部创建预订,而该组件在Observable
完成之前被销毁(或未“长久”)的情况。
I read this as meaning if we subscribe to an http
request or an observable that emits 10 values and our component is destroyed before that http
request returns or the 10 values have been emitted, we are still ok! 我的意思是,如果我们订阅一个http
请求或一个发出10个值的可观察对象,并且在该http
请求返回或发出10个值之前销毁了我们的组件,我们还是可以的!
When the request does return or the 10th value is finally emitted the Observable
will complete and all resources will be cleaned up. 当请求确实返回或最终发出第十个值时, Observable
将完成,所有资源将被清理。
Source 3 来源3
If we look at this example from the same Rangle guide we can see that the Subscription
to route.params
does require an unsubscribe()
because we don't know when those params
will stop changing (emitting new values). 如果我们从相同的Rangle指南中查看此示例 ,则可以看到对route.params
的Subscription
确实需要unsubscribe()
因为我们不知道这些params
何时会停止更改(发出新值)。
The component could be destroyed by navigating away in which case the route params will likely still be changing (they could technically change until the app ends) and the resources allocated in subscription would still be allocated because there hasn't been a completion
. 通过导航可以破坏该组件,在这种情况下,路由参数可能仍会更改(它们可能会在技术上更改,直到应用结束),并且由于尚未completion
因此仍将分配订阅中分配的资源。
#5楼
You don't need to have bunch of subscriptions and unsubscribe manually. 您不需要一堆订阅并手动取消订阅。 Use Subject and takeUntil combo to handle subscriptions like a boss: 使用Subject和takeUntil组合可像老板一样处理订阅:
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()
}
}
Alternative approach , which was proposed by @acumartini in comments , uses takeWhile instead of takeUntil . @acumartini在评论中提出的 替代方法使用takeWhile而不是takeUntil 。 You may prefer it, but mind that this way your Observable execution will not be cancelled on ngDestroy of your component (eg when you make time consuming calculations or wait for data from server). 您可能更喜欢它,但是请注意,这样一来,您的组件的ngDestroy上的Observable执行将不会被取消(例如,当您进行耗时的计算或等待服务器中的数据时)。 Method, which is based on takeUntil , doesn't have this drawback and leads to immediate cancellation of request. 基于takeUntil的方法没有此缺点,并导致立即取消请求。 Thanks to @AlexChe for detailed explanation in comments . 感谢@AlexChe在评论中提供详细的解释 。
So here is the 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
}
}
#6楼
I tried seangwright's solution (Edit 3) 我尝试了seangwright的解决方案(编辑3)
That is not working for Observable that created by timer or interval. 这不适用于计时器或时间间隔创建的Observable。
However, i got it working by using another approach: 但是,我通过使用另一种方法使其工作:
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();
}
}
}