什么时候我应该存储订阅实例和调用unsubscribe()在ngOnDestroy生命周期,什么时候我可以简单地忽略它们?
保存所有订阅会给组件代码带来很多麻烦。
HTTP客户端指南忽略这样的订阅:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
同时,《航路指南》指出:
最终,我们会航行到别的地方。路由器将从DOM中移除这个组件并销毁它。在那之前,我们得把自己弄干净。具体来说,我们必须在Angular销毁该组件之前取消订阅。如果不这样做,可能会产生内存泄漏。
我们在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();
}
你不需要有一堆订阅和取消手动订阅。使用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()
}
}
@acumartini在评论中提出了另一种方法,使用takeWhile而不是takeUntil。你可能更喜欢它,但要注意,这样你的Observable的执行就不会在组件的ngDestroy上被取消(例如,当你进行耗时的计算或等待来自服务器的数据时)。方法没有这个缺陷,它会导致立即取消请求。感谢@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 */ })
}
ngOnDestroy() {
this.alive = false
}
}
在我的情况下,我使用了@seanwright提出的解决方案的变化:
https://github.com/NetanelBasal/ngx-take-until-destroy
这是ngx-rocket / starter-kit项目中使用的文件。你可以在这里访问until-destroyed.ts
组件看起来是这样的
/**
* RxJS operator that unsubscribe from observables on destory.
* Code forked from https://github.com/NetanelBasal/ngx-take-until-destroy
*
* IMPORTANT: Add the `untilDestroyed` operator as the last one to
* prevent leaks with intermediate observables in the
* operator chain.
*
* @param instance The parent Angular component or object instance.
* @param destroyMethodName The method to hook on (default: 'ngOnDestroy').
*/
import { untilDestroyed } from '../../core/until-destroyed';
@Component({
selector: 'app-example',
templateUrl: './example.component.html'
})
export class ExampleComponent implements OnInit, OnDestroy {
ngOnInit() {
interval(1000)
.pipe(untilDestroyed(this))
.subscribe(val => console.log(val));
// ...
}
// This method must be present, even if empty.
ngOnDestroy() {
// To protect you, an error will be thrown if it doesn't exist.
}
}
出于性能原因,总是建议从你的可观察订阅中取消订阅,以避免内存泄漏,有不同的方法,
顺便说一下,我读了大部分的答案,我没有发现有人在谈论异步管道,推荐在Angular应用中使用Rxjs模式,因为它在离开组件时会自动提供订阅和订阅,而这些订阅将被销毁:
请找到一个如何实现它的例子
app.compoennt.ts:
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { BookService } from './book.service';
import { Book } from './book';
@Component({
selector: 'app-observable',
templateUrl: './observable.component.html'
})
export class AppComponent implements OnInit {
books$: Observable<Book[]>
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.books$ = this.bookService.getBooksWithObservable();
}
}
app.compoennt.html:
<h3>AsyncPipe with Promise using NgFor</h3>
<ul>
<li *ngFor="let book of books$ | async" >
Id: {{book?.id}}, Name: {{book?.name}}
</li>
</ul>
订阅类有一个有趣的特性:
表示一个一次性资源,比如Observable的执行。订阅有一个重要的方法,即unsubscribe,它不接受参数,只处理订阅所持有的资源。
此外,可以通过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();
}
}