Ser*_*hon 653 subscription observable rxjs angular angular-component-life-cycle
我应该何时存储Subscription
实例并unsubscribe()
在NgOnDestroy生命周期中调用,何时可以忽略它们?
保存所有订阅会在组件代码中引入很多混乱.
HTTP客户端指南忽略这样的订阅:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
Run Code Online (Sandbox Code Playgroud)
同时," 航线与导航指南"说:
最终,我们会在其他地方导航.路由器将从DOM中删除此组件并将其销毁.在此之前我们需要自己清理.具体来说,我们必须在Angular破坏组件之前取消订阅.如果不这样做可能会造成内存泄漏.
我们取消订阅我们
Observable
的ngOnDestroy
方法.
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();
}
Run Code Online (Sandbox Code Playgroud)
sea*_*ght 926
最近在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的方式.
也就是说,我们现在主要需要解决方案,所以这里有一些其他资源.
takeUntil()
来自RxJs核心团队成员Nicholas Jamieson 的模式建议以及帮助实施该模式的tslint规则.https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef
轻量级npm包,公开一个Observable操作符,该操作符将一个组件instance(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和Presentational组件.然后| async
,您可以将Feature组件中的Observable转换Input
为Presentational组件,这意味着无需在任何地方进行订阅.在这里阅读更多关于这种方法
我在NGConf与Ward Bell谈到了这个问题(我甚至向他展示了这个答案,他说这是正确的)但是他告诉我Angular的文档团队解决了这个未发表的问题(尽管他们正在努力让它获得批准) ).他还告诉我,我可以用即将推出的官方建议更新我的答案.
我们今后应该使用的解决方案是向private ngUnsubscribe = new Subject();
所有在类代码中.subscribe()
调用Observable
s的组件添加一个字段.
然后我们调用this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
我们的ngOnDestroy()
方法.
秘密酱(正如@metamaker已经指出的那样)是takeUntil(this.ngUnsubscribe)
在每次调用之前.subscribe()
调用,这将保证在组件被销毁时清理所有订阅.
例:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
注意:将takeUntil
运算符添加为最后一个运算符非常重要,以防止运算符链中的中间可观察对象发生泄漏.
来源5
Angular教程,路由章节现在陈述如下:"路由器管理它提供的可观察量并本地化订阅.订阅在组件被销毁时被清除,防止内存泄漏,因此我们不需要取消订阅路线可以观察到." - Mark Rajcok
这里讨论了关于路由器可观测量的Angular文档的Github问题,其中Ward Bell提到所有这些的澄清正在进行中.
来源4
在来自NgEurope的视频中, Rob Wormald还表示您不需要取消订阅Router Observables.他还提到了http
服务,并ActivatedRoute.params
在此从2016年11月的视频.
TLDR:
对于这个问题,有(2)种Observables
- 有限值和无限值.
http
Observables
产生有限(1)值,类似DOM的东西event listener
Observables
产生无限值.
如果你手动调用subscribe
(不使用异步管道),那么unsubscribe
从无限 Observables
.
不要担心有限的,RxJs
会照顾他们.
来源1
我在这里找到了Angular Gitter的Rob Wormald的回答.
他说(我为了清晰而重组,重点是我的)
如果它是单值序列(如http请求),则不需要手动清理(假设您手动在控制器中订阅)
我应该说"如果它的序列完成 "(其中单个值序列,一个la http是一个)
如果它是一个无限序列,你应该取消订阅异步管道为你做的
他还在这个关于Observables的youtube视频中提到they clean up after themselves
......在Observables 的背景下complete
(像Promises一样,因为它们总是产生1个值并结束 - 我们从不担心取消订阅Promises以确保它们清理xhr
事件听众,对吗?).
来源2
同样在Angular 2的Rangle指南中,它会读取
在大多数情况下,我们不需要显式调用unsubscribe方法,除非我们想要提前取消或我们的Observable的寿命比我们的订阅更长.Observable操作符的默认行为是在发布.complete()或.error()消息后立即处理订阅.请记住,RxJS的设计大多数时候都是以"一见不醒"的方式使用.
这句话our Observable has a longer lifespan than our subscription
什么时候适用?
它适用于在Observable
完成之前(或之前不长)之前销毁的组件内创建订阅的情况.
我读到这意味着如果我们订阅一个http
请求或一个发出10个值的observable并且我们的组件在该http
请求返回之前被销毁或者已经发出10个值,我们仍然可以!
当请求返回或最终发出第10个值时,Observable
将完成并清除所有资源.
来源3
如果我们从同一个Rangle指南中查看这个例子,我们可以看到Subscription
to route.params
确实需要一个,unsubscribe()
因为我们不知道它们何时params
会停止更改(发出新值).
该组件可以通过导航来销毁,在这种情况下,路由参数可能仍会改变(它们可能在技术上改变直到应用程序结束)并且订阅中分配的资源仍然会被分配,因为没有completion
.
met*_*ker 80
您不需要手动拥有大量订阅和取消订阅.使用RxJS.Subject和takeUntil组合来处理像boss这样的订阅:
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()
}
}
Run Code Online (Sandbox Code Playgroud)
@acumartini在评论中提出的替代方法使用takeWhile而不是takeUntil.您可能更喜欢它,但请注意,这样您的Observable执行将不会在组件的ngDestroy上被取消(例如,当您进行耗时的计算或等待来自服务器的数据时).基于takeUntil的方法没有这个缺点,导致立即取消请求.感谢@AlexChe在评论中的详细解释.
所以这是代码:
@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
}
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*ens 64
Subscription类有一个有趣的特性:
表示可处理的资源,例如Observable的执行.订阅有一个重要的方法,取消订阅,不带参数,只是处置订阅所持有的资源.
此外,订阅可以通过add()方法组合在一起,该方法将子订阅附加到当前订阅.当订阅被取消订阅时,其所有子女(及其孙子女)也将被取消订阅.
您可以创建一个聚合订阅对象,该对象将您的所有订阅分组.您可以通过创建一个空的Subscription并使用其add()
方法向其添加订阅来完成此操作.当您的组件被销毁时,您只需要取消订阅聚合订阅.
@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();
}
}
Run Code Online (Sandbox Code Playgroud)
Mou*_*eer 29
关于Angular组件内可观察的取消订阅的一些最佳实践:
订阅组件中的observable时,您几乎总是安排在组件被销毁时取消订阅.
有一些特殊的可观测量,这是没有必要的.ActivatedRoute可观察量是例外.
ActivatedRoute及其可观察对象与路由器本身绝缘.当不再需要路由器时,路由器会销毁路由组件,并且注入的ActivatedRoute会随之消失.
无论如何,请随意取消订阅.它是无害的,从来都不是一种糟糕的做法.
并在回应以下链接:
http
可观察的评论我收集了一些关于Angular组件中可观察的取消订阅的最佳实践,以便与您分享:
http
观察到的退订是有条件的,我们应该考虑到"认购回调"的效果组件在由个案基础上破坏后被运行.我们知道有角度取消订阅并清理http
可观察物本身(1),(2).虽然从资源的角度来看这是真的,但它只讲述了一半的故事.假设我们正在讨论http
从组件内部直接调用,http
响应时间比需要的时间长,因此用户关闭了组件.该subscribe()
处理器将组件是否关闭和销毁仍然甚至称.这可能会产生不必要的副作用,在更糟糕的情况下会使应用程序状态中断.如果回调中的代码试图调用刚被处理掉的东西,它也会导致异常.然而,偶尔他们是偶然的.比如,假设您正在创建一个电子邮件客户端,并在电子邮件发送完成后触发声音 - 即使组件已关闭,您仍然希望发生这种情况(8).AsyncPipe
尽可能多地使用,因为它会自动取消订阅组件销毁时的可观察性.ActivatedRoute
可观察对象,route.params
如果它们是在嵌套(在组件选择器中添加内部tpl)或动态组件内订阅的,因为只要父/主机组件存在,它们可能会多次订阅.无需在上述Routing & Navigation
文档中提到的其他方案中取消订阅.OnDestroy
根据文档使用生命周期钩子取消订阅,该生命周期钩子将在服务被销毁时调用.takeUntil
(3) ,或者你可以使用这个npm
包在提到(4)最简单的方法,从观测量的角度退订.FormGroup
像form.valueChanges
和的观察者form.statusChanges
Renderer2
服务的可观察性renderer2.listen
HostListener
角度关注如果需要删除事件侦听器,并防止由于事件绑定导致的任何潜在内存泄漏.一个不错的最后提示:如果您不知道是否正在自动取消订阅/完成一个observable,请添加一个complete
回调subscribe(...)
并检查它是否在组件被销毁时被调用.
Chu*_*Sun 16
这取决于.如果通过调用someObservable.subscribe()
,您开始占用一些必须在组件的生命周期结束后手动释放的资源,那么您应该调用theSubscription.unsubscribe()
以防止内存泄漏.
让我们仔细看看你的例子:
getHero()
返回结果http.get()
.如果您查看angular 2 源代码,请http.get()
创建两个事件侦听器:
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
Run Code Online (Sandbox Code Playgroud)
通过调用unsubscribe()
,您可以取消请求以及听众:
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
Run Code Online (Sandbox Code Playgroud)
请注意,这_xhr
是特定于平台的,但我认为可以安全地假设它是XMLHttpRequest()
您的情况.
通常,这足以证明手动unsubscribe()
呼叫.但根据WHATWG规范,XMLHttpRequest()
即使有附加事件监听器,它也会在"完成"后进行垃圾收集.所以我想这就是为什么angular 2官方指南省略unsubscribe()
并让GC清理听众.
至于你的第二个例子,它取决于你的实现params
.截至今天,角度官方指南不再显示取消订阅params
.我再次查看了src,发现这params
只是一个BehaviorSubject.由于没有使用事件监听器或定时器,并且没有创建全局变量,因此省略它应该是安全的unsubscribe()
.
你的问题的底线是始终调用unsubscribe()
防止内存泄漏,除非你确定observable的执行不会创建全局变量,添加事件监听器,设置定时器,或做任何导致内存泄漏的事情.
如有疑问,请查看该可观察的实现.如果observable已经将一些清理逻辑写入其中unsubscribe()
,这通常是构造函数返回的函数,那么您有充分的理由认真考虑调用unsubscribe()
.
小智 6
Angular 2官方文档提供了何时取消订阅以及何时可以安全忽略的说明.看看这个链接:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
查找标题为Parent的段落,子项通过服务进行通信,然后是蓝色框:
请注意,我们捕获订阅并在AstronautComponent销毁时取消订阅.这是一个内存泄漏保护步骤.此应用程序中没有实际风险,因为AstronautComponent的生命周期与应用程序本身的生命周期相同.在更复杂的应用程序中,这并非总是如此.
我们不会将此保护添加到MissionControlComponent,因为作为父级,它控制MissionService的生命周期.
我希望这可以帮助你.
对于在发出结果后直接完成的可观察量,AsyncSubject
例如来自 http 请求的可观察量等,您不需要取消订阅。调用这些并没有什么坏处unsubscribe()
,但如果可观察的是closed
unsubscribe 方法将根本不执行任何操作:
if (this.closed) {
return;
}
Run Code Online (Sandbox Code Playgroud)
当您拥有长期存在的可观察量并随着时间的推移发出多个值(例如 aBehaviorSubject
或 a ReplaySubject
)时,您需要取消订阅以防止内存泄漏。
您可以轻松创建一个可观察量,该可观察量在使用管道运算符从此类长期存在的可观察量发出结果后直接完成。在这里的一些答案中take(1)
提到了管道。但我更喜欢管子first()
。不同之处take(1)
在于它将:
EmptyError
如果 Observable 在发送任何下一个通知之前完成,则向观察者的错误回调传递一个错误回调。
第一个管道的另一个优点是您可以传递一个谓词来帮助您返回满足特定条件的第一个值:
const predicate = (result: any) => {
// check value and return true if it is the result that satisfies your needs
return true;
}
observable.pipe(first(predicate)).subscribe(observer);
Run Code Online (Sandbox Code Playgroud)
First 将在发出第一个值后直接完成(或者在传递函数参数时满足您的谓词的第一个值),因此无需取消订阅。
有时你不确定你是否有一个长期存在的可观察对象。我并不是说这是一个好的做法,但您可以随时添加管道first
以确保不需要手动取消订阅。在可观察对象上添加一个first
仅发出一个值的附加管道并没有什么坏处。
在开发过程中,如果源可观察对象发出多个事件,您可以使用将失败的管道。single
这可以帮助您探索 observable 的类型以及是否有必要取消订阅。
observable.pipe(single()).subscribe(observer);
Run Code Online (Sandbox Code Playgroud)
和看起来非常相似,两个管道都可以采用可选谓词,但差异很重要,并且在这个 stackoverflow 答案中first
很好地总结了:single
第一的
第一个项目出现后就会发出。之后将立即完成。
单身的
如果源可观察对象发出多个事件,则会失败。
请注意, 我试图在回答中尽可能准确和完整,并参考官方文档,但如果缺少重要内容,请发表评论......
Angular 16引入了一个新函数来减轻对 observable 的破坏takeUntilDestroyed
。
data$ = http.get('...').pipe(takeUntilDestroyed()).subscribe(...);
Run Code Online (Sandbox Code Playgroud)
默认情况下,应该在构造函数内部调用它。要想在其他地方使用它,那DestroyRef
就是必要的。
destroyRef = inject(DestroyRef);
ngOnInit(){
data$ = http.get('...').subscribe(...)
this.destroyRef.onDestroy(() => {
data$.unsubscribe()
})
}
Run Code Online (Sandbox Code Playgroud)
另一种通用方法:
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
}
}
Run Code Online (Sandbox Code Playgroud)
并使用:
@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));
}
}
Run Code Online (Sandbox Code Playgroud)
小智 5
订阅本质上只有一个 unsubscribe() 函数来释放资源或取消 Observable 执行。 在 Angular 中,当组件被销毁时,我们必须取消订阅 Observable。幸运的是,Angular 有一个 ngOnDestroy 钩子,可以在组件被销毁之前调用,这使得开发人员能够在此处提供清理人员,以避免挂起订阅、打开门户以及将来可能出现的问题
@Component({...})
export class AppComponent implements OnInit, OnDestroy {
subscription: Subscription
ngOnInit () {
var observable = Rx.Observable.interval(1000);
this.subscription = observable.subscribe(x => console.log(x));
}
ngOnDestroy() {
this.subscription.unsubscribe()
}
}
Run Code Online (Sandbox Code Playgroud)
我们将 ngOnDestroy 添加到 AppCompoennt 中,并在 this.subscription Observable 上调用取消订阅方法
如果有多个订阅:
@Component({...})
export class AppComponent implements OnInit, OnDestroy {
subscription1$: Subscription
subscription2$: Subscription
ngOnInit () {
var observable1$ = Rx.Observable.interval(1000);
var observable2$ = Rx.Observable.interval(400);
this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x));
this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x));
}
ngOnDestroy() {
this.subscription1$.unsubscribe()
this.subscription2$.unsubscribe()
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
149908 次 |
最近记录: |