我何时应该存储 Subscription
实例并在NgOnDestroy生命周期中调用 unsubscribe()
什么时候可以忽略它们?
保存所有订阅会在组件代码中引入很多混乱 .
HTTP Client Guide忽略这样的订阅:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
在同一时间Route & Navigation Guide说:
最终,我们会在其他地方导航 . 路由器将从DOM中删除此组件并将其销毁 . 在此之前我们需要自己清理 . 具体来说,我们必须在Angular破坏组件之前取消订阅 . 如果不这样做可能会造成内存泄漏 . 我们在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();
}
14 回答
您不需要手动进行大量订阅和取消订阅 . 使用RxJS.Subject和takeUntil combo来处理像boss这样的订阅:
Alternative approach ,建议by @acumartini in comments,使用takeWhile而不是takeUntil . 您可能更喜欢它,但请注意,这样您的Observable执行将不会在组件的ngDestroy上被取消(例如,当您花费大量时间计算或等待来自服务器的数据时) . 方法,基于takeUntil,没有这个缺点,导致立即取消请求 . Thanks to @AlexChe for detailed explanation in comments .
所以这是代码:
Some of the best practices regarding observables unsubscriptions inside Angular components:
来自Routing & Navigation的报价
并在回应以下链接:
(1)Should I unsubscribe from Angular 2 Http Observables?
(2)Is it necessary to unsubscribe from observables created by Http methods?
(3)RxJS: Don’t Unsubscribe
(4)The easiest way to unsubscribe from Observables in Angular
(5)Documentation for RxJS Unsubscribing
(6)Unsubscribing in a service is kind of pointless since there is no chance of memory leaks
(7)Do we need to unsubscribe from observable that completes/errors-out?
(8)A comment about the http observable
我收集了一些关于Angular组件中可观察的取消订阅的最佳实践,以便与您分享:
http
可观察的取消订阅是有条件的,我们应该考虑在逐个销毁组件之后运行'subscribe callback'的效果 . 我们知道有角度取消订阅并清理http
observable本身(1),(2) . 虽然从资源的角度来看这是真实的,但它只讲述了一半的故事 . 让's say we'谈论直接从组件中调用http
,并且http
响应花费的时间比需要的时间长,因此用户关闭了组件 . 即使组件已关闭并销毁,仍将调用subscribe()
处理程序 . 这可能会产生不必要的副作用,在更糟糕的情况下会使应用程序状态中断 . 如果回调中的代码试图调用刚处理掉的东西,它也会导致异常 . 然而,有时他们是偶然的 . 比如,让's say you'重新创建一个电子邮件客户端,并在电子邮件发送完成后触发声音 - 即使组件已关闭(8),您仍然希望发生这种情况 .无需取消订阅完成或错误的可观察对象 . 但是,这样做没有害处(7) .
尽可能使用
AsyncPipe
因为它会自动取消订阅组件销毁时的observable .取消订阅
ActivatedRoute
observables,如route.params
,如果它们是在嵌套(在组件选择器中添加内部tpl)或动态组件内订阅的,因为只要父/主机组件存在,它们可能会多次订阅 . 无需在Routing & Navigation docs上面的引用中提到的其他方案中取消订阅它们 .取消订阅通过Angular服务公开的组件之间共享的全局可观察对象,例如,只要组件初始化,它们可能会多次订阅 .
无需取消订阅应用程序作用域服务的内部可观察量,因为此服务永远不会被销毁,没有真正的理由取消订阅它并且没有内存泄漏的可能性 . (6) .
Note: 关于作用域服务,即组件提供程序,它们在销毁组件时被销毁 . 在这种情况下,如果我们订阅了这个提供程序中的任何observable,我们应该考虑使用OnDestroy生命周期钩子取消订阅,根据文档销毁服务时将调用它 .
使用抽象技术可以避免因取消订阅而导致的任何代码混乱 . 您可以使用
takeUntil
(3)管理您的订阅,也可以使用(4) The easiest way to unsubscribe from Observables in Angular中提到的npm
package .始终取消订阅
FormGroup
像form.valueChanges
和form.statusChanges
这样的可观测量始终取消订阅
Renderer2
服务的可观察量,如renderer2.listen
取消订阅其他可观察的其他内容作为内存泄漏防护步骤,直到Angular Docs明确告诉我们哪些observable不需要取消订阅(检查问题:(5) Documentation for RxJS Unsubscribing (Open)) .
Bonus:始终使用Angular方法绑定
HostListener
之类的事件,因为如果需要,可以很好地删除事件侦听器,并防止因事件绑定而导致的任何潜在内存泄漏 .A nice final tip :如果您不知道是否自动取消订阅/已完成observable,请将
complete
回调添加到subscribe(...)
并检查是否在销毁该组件时调用它 .由于seangwright的解决方案(编辑3)似乎非常有用,我还发现将此功能打包到基本组件中很麻烦,并且暗示其他项目团队成员记得在ngOnDestroy上调用super()来激活此功能 .
这个答案提供了一种从超级调用中解脱出来的方法,并使“componentDestroyed $”成为基本组件的核心 .
然后您可以自由使用此功能,例如:
正式的编辑#3答案(和变体)运作良好,但让我感觉到的是围绕可观察订阅的业务逻辑的“混乱” .
这是使用包装器的另一种方法 .
文件subscribeAndGuard.ts用于创建一个新的Observable扩展来包装
.subscribe()
并在其中包装ngOnDestroy()
.用法与
.subscribe()
相同,但引用该组件的其他第一个参数除外 .这是一个包含两个订阅的组件,一个包含包装器,另一个包含订阅包 . 唯一需要注意的是 must implement OnDestroy (如果需要,还有空体),否则Angular不知道调用包装版本 .
一个演示插件是here
An additional note: Re Edit 3 - 'Official'解决方案,这可以通过在订阅之前使用takeWhile()而不是takeUntil()来简化,并且在ngOnDestroy中使用简单的布尔值而不是另一个Observable .
这取决于 . 如果通过调用
someObservable.subscribe()
,您开始占用一些必须在组件的生命周期结束时手动释放的资源,那么您应该调用theSubscription.unsubscribe()
以防止内存泄漏 .让我们仔细看看你的例子:
getHero()
返回http.get()
的结果 . 如果你看一下angular 2 source code,http.get()
会创建两个事件监听器:通过调用
unsubscribe()
,您可以取消请求以及侦听器:请注意
_xhr
是特定于平台的,但我认为在您的情况下假设它是XMLHttpRequest()
是安全的 .通常情况下,这足以保证手动拨打电话 . 但根据WHATWG spec,
XMLHttpRequest()
一旦被"done"进行垃圾收集,即使附加了事件监听器也是如此 . 所以我想这就是为什么angular 2官方指南省略unsubscribe()
并让GC清理听众 .至于你的第二个例子,它取决于
params
的实现 . 截至今天,角度官方指南不再显示取消订阅params
. 我再次调查了src,发现params
只是一个BehaviorSubject . 由于没有使用事件侦听器或计时器,并且没有创建全局变量,因此省略unsubscribe()
应该是安全的 .您的问题的底线是始终将
unsubscribe()
调用为防止内存泄漏,除非您确定observable的执行不会创建全局变量,添加事件侦听器,设置计时器或执行任何其他导致内存的操作泄漏 .如有疑问,请查看该可观察的实现 . 如果observable在其
unsubscribe()
中编写了一些清理逻辑,这通常是构造函数返回的函数,那么你有充分的理由认真考虑调用unsubscribe()
.Angular 2官方文档提供了何时取消订阅以及何时可以安全忽略的说明 . 看看这个链接:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
查找 Headers 为 Parent and children communicate via a service 的段落,然后是蓝色框:
我希望这可以帮助你 .
我试过seangwright的解决方案(编辑3)
这对于由计时器或间隔创建的Observable不起作用 .
但是,我通过使用另一种方法使其工作:
您通常需要在组件被销毁时取消订阅,但Angular将会越来越多地处理它,例如在新的Angular4的次要版本,他们有此部分用于路由取消订阅:
另外下面的例子是一个很好的例子,从Angular创建一个组件并在之后销毁它,看看组件如何实现OnDestroy,如果你需要onInit,你也可以在你的组件中实现它,比如implements
OnInit, OnDestroy
基于:Using Class inheritance to hook to Angular 2 component lifecycle
另一种通用方法:
并使用:
---编辑4 - 其他资源(2018/09/01)
在最近的一集Adventures in Angular中,Ben Lesh和Ward Bell讨论了如何/何时取消订阅组件的问题 . 讨论从大约1:05:30开始 .
沃德提到
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
.作为回应,Ben提到现在正在进行讨论以允许Observables挂钩Angular组件生命周期事件,而Ward建议生命周期事件的Observable,组件可以订阅这些事件以了解何时完成作为组件内部状态维护的Observable .
也就是说,我们现在主要需要解决方案,所以这里有一些其他资源 .
来自RxJs核心团队成员Nicholas Jamieson的
takeUntil()
模式的建议以及帮助实施它的tslint规则 . https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef轻量级npm包,它公开一个Observable运算符,该运算符将一个组件实例(
this
)作为参数,并在ngOnDestroy
期间自动取消订阅 . https://github.com/NetanelBasal/ngx-take-until-destroy如果您没有进行AOT构建,那么上述的另一种变化与人体工程学稍好一些(但我们现在都应该做AOT) . https://github.com/smnbbrv/ngx-rx-collector
自定义指令
*ngSubscribe
,其作用类似于异步管道但在模板中创建嵌入式视图,因此您可以在整个模板中引用'unwrapped'值 . https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f我在对Nicholas博客的评论中提到过度使用
takeUntil()
可能表明你的组件试图做太多,并且应该考虑将现有组件分成 Feature 和 Presentational 组件 . 然后,您可以将功能组件中的Observable| async
显示为演示组件的Input
,这意味着在任何地方都不需要订阅 . 阅读有关此方法的更多信息here---编辑3 - '官方'解决方案(2017/04/09)
我在NGConf与Ward Bell谈到了这个问题(我甚至向他展示了这个答案,他说这是正确的)但是他告诉我Angular的文档团队解决了这个未发表的问题(虽然他们正在努力让它获得批准) ) . 他还告诉我,我可以通过即将发布的官方建议更新我的答案 .
我们今后应该使用的解决方案是将
private ngUnsubscribe = new Subject();
字段添加到在其类代码中对Observable
进行.subscribe()
调用的所有组件 .然后我们在
ngOnDestroy()
方法中调用this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
.秘密酱(正如@metamaker所述)是在我们的每个
.subscribe()
调用之前调用takeUntil(this.ngUnsubscribe)
,这将保证在组件被销毁时清除所有订阅 .例:
Note: 将
takeUntil
运算符添加为最后一个运算符非常重要,以防止运算符链中的中间可观察对象泄漏 .---编辑2(2016/12/28)
Source 5
Angular教程,路由章节现在声明如下:"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." - Mark Rajcok
这是关于路由器可观测量的Angular文档的Github问题的discussion,其中Ward Bell提到所有这些的澄清正在进行中 .
---编辑1
Source 4
在这个video from NgEurope中,Rob Wormald还说你不需要取消订阅Router Observables . 他还提到
http
服务和ActivatedRoute.params
在这个video from November 2016 .---原始答案
TLDR:
对于这个问题,有(2)种
Observables
- finite 值和 infinite 值 .http
Observables
产生 finite (1)值和类似DOMevent listener
Observables
产生 infinite 的值 .如果您手动调用
subscribe
(不使用异步管道),则unsubscribe
来自 infiniteObservables
.不要担心 finite ,
RxJs
会照顾他们 .Source 1
我在Angular的Gitter here中找到了Rob Wormald的回答 .
他表示(我为了清晰而重组,重点是我的)
此外,他在Observables中提及
they clean up after themselves
......在Observables的背景下complete
(像Promises一样,因为它们总是产生1个值并结束 - 我们从不担心取消订阅Promises以确保它们清理xhr
事件监听器,对吗?) .Source 2
它也在Rangle guide to Angular 2中读取
什么时候短语
our Observable has a longer lifespan than our subscription
适用?当在
Observable
完成之前(或之前'long'之前)销毁的组件内创建订阅时,它适用 .如果我们订阅了一个
http
请求或一个发出10个值的observable并且在http
请求返回之前销毁了我们的组件或者已经发出了10个值,那么我认为这意味着我们仍然可以!当请求返回或最终发出第10个值时,
Observable
将完成,所有资源将被清除 .Source 3
如果我们从同一个Rangle指南中查看this example,我们可以看到
Subscription
到route.params
确实需要unsubscribe()
,因为我们不知道params
何时会停止更改(发出新值) .可以通过导航来销毁该组件,在这种情况下,路由参数可能仍在改变(它们可能在技术上改变直到应用程序结束)并且仍然会分配订阅中分配的资源,因为没有
completion
.上述情况的另一个简短补充是:
Subscription类有一个有趣的特性:
您可以创建一个聚合订阅对象,该对象将您的所有订阅分组 . 您可以通过创建一个空的Subscription并使用其
add()
方法向其添加订阅来完成此操作 . 当您的组件被销毁时,您只需要取消订阅聚合订阅 .在@seangwright的答案之后,我在组件中订阅了've written an abstract class that handles 229360 observables':
要使用它,只需在角度组件中扩展它并按如下方式调用
subscribe()
方法:它也像往常一样接受错误和完整的回调,一个观察者对象,或者根本不接受回调 . 如果您还在子组件中实现该方法,请记得调用
super.ngOnDestroy()
.在这里找到Ben Lesh的另一个参考:RxJS: Don’t Unsubscribe .
我喜欢最后两个答案,但如果子类在
ngOnDestroy
中引用"this"
,我遇到了一个问题 .我修改它是这样,看起来它解决了这个问题 .