问题描述
我应该在 ngOnDestroy
生命周期中何时存储 Subscription
实例并调用 unsubscribe()
,何时可以忽略它们?
保存所有订阅会给组件代码带来很多混乱.
HTTP 客户端指南 忽略这样的订阅:>
getHeroes() {this.heroService.getHeroes().订阅(英雄 =>this.heroes = 英雄,错误 =>this.errorMessage = <any>error);}
同时路由&导航指南说:
最终,我们将导航到其他地方.路由器将从 DOM 中删除该组件并销毁它.在这种情况发生之前,我们需要自己清理干净.具体来说,我们必须在 Angular 销毁组件之前取消订阅.否则可能会造成内存泄漏.
我们在 ngOnDestroy
方法中取消订阅我们的 Observable
.
private sub: any;ngOnInit() {this.sub = this.route.params.subscribe(params => {让 id = +params['id'];//(+) 将字符串 'id' 转换为数字this.service.getHero(id).then(hero => this.hero = hero);});}ngOnDestroy() {this.sub.unsubscribe();}
TL;DR
对于这个问题,有两种 Observables - 有限值和无限值.
http
Observables 产生有限 (1) 值和类似 DOM 事件侦听器的东西 Observable 产生无限 值.
如果您手动调用 subscribe
(不使用异步管道),则从 infinite Observables 中unsubscribe
.
不要担心有限,RxJ 会照顾好它们.
来源:
我在 Angular 的 Gitter 此处找到了 Rob Wormald 的回答.
他说(为了清晰起见,我重新组织了一下,重点是我的):
如果它的单值序列(如 http 请求)不需要手动清理(假设您手动订阅控制器)
我应该说如果它是一个完成的序列";(其中单值序列,一个 la http,是一个)
如果它是一个无限序列,你应该取消订阅异步管道为你做的
他还在这个 YouTube 视频中提到了他们清理了 Observables在完成的 Observables 的上下文中(就像 Promises,它总是完成,因为它们总是产生一个值和结束 - 我们从不担心取消订阅 Promises以确保他们清理 XHR 事件侦听器,对吗?)
也在 Angular 2 的 Rangle 指南 中写道
在大多数情况下,我们不需要显式调用
unsubscribe
方法,除非我们想提前取消或者我们的Observable
的生命周期比我们的订阅更长.Observable
操作符的默认行为是在.complete()
或.error()
消息发布后立即处理订阅.请记住,RxJS 旨在用于即发即忘"的情况.大多数时候都很时尚.我们的
Observable
的生命周期比我们的订阅更长"这句话何时适用?它适用于在组件内部创建订阅时,该组件在 Observable 完成之前(或不久之前)被销毁.
我认为这意味着如果我们订阅一个
http
请求或一个发出 10 个值的 Observable 并且我们的组件在http
请求返回或 10 个值之前被销毁已经发出,我们还好!当请求确实返回或最终发出第 10 个值时,Observable 将完成并清理所有资源.
如果我们从同一个 Rangle 看这个例子我们可以看到订阅
route.params
确实需要一个unsubscribe()
因为我们不知道那些params
什么时候会停止改变(发出新值).组件可能会通过导航而被破坏,在这种情况下,路由参数可能仍在更改(它们在技术上可能会更改,直到应用程序结束)并且订阅中分配的资源仍将被分配,因为没有 完成.
在来自 NgEurope 的 这段视频中,Rob Wormald 还说你不需要取消订阅 Router Observables.他还在 http 服务和
ActivatedRoute.params
>此视频来自 2016 年 11 月.Angular 教程,路由章节 现在声明如下:
Router
管理它提供的 observables 并本地化订阅.订阅在组件被销毁时被清除,防止内存泄漏,所以我们不需要取消订阅params
Observable
.这是关于 GitHub 问题的讨论关于 Router Observables 的 Angular 文档,其中 Ward Bell 提到正在对所有这些进行澄清.
我在 NGConf 上与 Ward Bell 谈过这个问题(我什至向他展示了这个他说是正确的答案),但他告诉我 Angular 的文档团队有一个未发表的问题的解决方案(尽管他们正在研究获得批准).他还告诉我,我可以根据即将发布的官方推荐更新我的 SO 答案.
我们都应该使用的解决方案是向所有具有 .subscribe()
调用 Observables 的组件添加一个 private ngUnsubscribe = new Subject();
字段在他们的班级代码中.
然后我们调用this.ngUnsubscribe.next();this.ngUnsubscribe.complete();
在我们的 ngOnDestroy()
方法中.
秘诀(如 @metamaker 所述)是调用 takeUntil(this.ngUnsubscribe)
在我们的每个 .subscribe()
调用之前,这将保证在组件销毁时所有订阅都将被清除.
示例:
import { Component, OnDestroy, OnInit } from '@angular/core';//RxJs 6.x+ 导入路径import { filter, startWith, takeUntil } from 'rxjs/operators';从'rxjs'导入{主题};从 '../books.service' 导入 { BookService };@成分({选择器:'应用程序书',templateUrl: './books.component.html'})导出类 BooksComponent 实现 OnDestroy、OnInit {私人 ngUnsubscribe = 新主题();构造函数(私有书籍服务:BookService){}ngOnInit() {this.booksService.getBooks().管道(从...开始([]),过滤器(书籍 => 书籍.长度 > 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();}}
注意:添加 takeUntil
操作符作为最后一个操作符很重要,以防止操作符链中的中间 Observables 泄漏.
最近,在一集中 Angular 中的冒险 Ben Lesh 和 Ward Bell 讨论了如何/何时取消订阅组件的问题.讨论从大约 1:05:30 开始.
Ward 提到现在有一个可怕的 takeUntil 舞蹈需要很多机器",Shai Reznik 提到Angular 处理一些订阅,比如 http 和路由"em>.
作为回应,Ben 提到现在正在讨论允许 Observables 挂钩到 Angular 组件生命周期事件,Ward 建议组件可以订阅生命周期事件的 Observable 作为了解何时完成作为组件维护的 Observables 的一种方式内部状态.
也就是说,我们现在主要需要解决方案,所以这里有一些其他资源.
来自 RxJs 核心团队成员 Nicholas Jamieson 的对
takeUntil()
模式的建议以及帮助执行它的 TSLint 规则:https://ncjamieson.com/avoiding-takeuntil-leaks/暴露一个 Observable 操作符的轻量级 npm 包,该操作符将组件实例 (
this
) 作为参数并在ngOnDestroy
期间自动取消订阅:https://github.com/NetanelBasal/ngx-take-until-destroy如果您不进行 AOT 构建(但我们现在都应该进行 AOT),则上述的另一种变体具有更好的人体工程学:https://github.com/smnbbrv/ngx-rx-collector
自定义指令
*ngSubscribe
的工作方式类似于异步管道,但会在您的模板中创建一个嵌入式视图,以便您可以在整个模板中引用解包"值:https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f
我在 Nicholas 博客的评论中提到,过度使用 takeUntil()
可能表明您的组件正在尝试做太多事情,并且将您现有的组件分成 应考虑功能和展示组件.然后你可以|将 Feature 组件中的 Observable 异步
到 Presentational 组件的 Input
中,这意味着在任何地方都不需要订阅.阅读有关此方法的更多信息 这里.
When should I store the Subscription
instances and invoke unsubscribe()
during the ngOnDestroy
life cycle and when can I simply ignore them?
Saving all subscriptions introduces a lot of mess into component code.
HTTP Client Guide ignore subscriptions like this:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
In the same time Route & Navigation Guide says that:
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();
}
TL;DR
For this question there are two kinds of Observables - finite value and infinite value.
http
Observables produce finite (1) values and something like a DOM event listener Observable produces infinite values.
If you manually call subscribe
(not using async pipe), then unsubscribe
from infinite Observables.
Don't worry about finite ones, RxJs will take care of them.
Sources:
I tracked down an answer from Rob Wormald in Angular's Gitter here.
He states (I reorganized for clarity and emphasis is mine):
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 one value and ending - we never worried about unsubscribing from Promises to make sure they clean up XHR event listeners, right?)
Also in the Rangle guide to Angular 2 it reads
When does the phrase "our
Observable
has a longer lifespan than our subscription" apply?It applies when a subscription is created inside a component which is destroyed before (or not 'long' before) the Observable completes.
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 thathttp
request returns or the 10 values have been emitted, we are still OK!When the request does return or the 10th value is finally emitted the Observable will complete and all resources will be cleaned up.
If we look at this example from the same Rangle guide we can see that the subscription to
route.params
does require anunsubscribe()
because we don't know when thoseparams
will stop changing (emitting new values).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.
In this video from NgEurope Rob Wormald also says you do not need to unsubscribe from Router Observables. He also mentions the
http
service andActivatedRoute.params
in this video from November 2016.The Angular tutorial, the Routing chapter now states the following:
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.
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). He also told me I could update my SO answer with the forthcoming official recommendation.
The solution we should all use going forward is to add a private ngUnsubscribe = new Subject();
field to all components that have .subscribe()
calls to Observables within their class code.
We then call this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
in our ngOnDestroy()
methods.
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.
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.
More recently, in an episode of Adventures in Angular Ben Lesh and Ward Bell discuss the issues around how/when to unsubscribe in a component. The discussion starts at about 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".
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.
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: https://ncjamieson.com/avoiding-takeuntil-leaks/Lightweight npm package that exposes an Observable operator that takes a component instance (
this
) as a parameter and automatically unsubscribes duringngOnDestroy
: 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): 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: 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. You can then | async
the Observable from the Feature component into an Input
of the Presentational component, which means no subscriptions are necessary anywhere. Read more about this approach here.
这篇关于Angular/RxJS 我什么时候应该取消订阅`Subscription`的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!