什么时候我应该存储订阅实例和调用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();
}
在我的情况下,我使用了@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 2官方文档提供了一个关于何时退订以及何时可以安全忽略的解释。看看这个链接:
https://angular.io/docs/ts/latest/cookbook/component-communication.html !# bidirectional-service
寻找标题为“父母和孩子通过服务通信”的段落,然后是蓝色方框:
注意,当AstronautComponent被销毁时,我们捕获了订阅并取消了订阅。这是一个内存泄漏保护步骤。在这个应用程序中没有实际的风险,因为AstronautComponent的生命周期与应用程序本身的生命周期相同。在更复杂的应用程序中,这并不总是正确的。
我们没有将这个守卫添加到MissionControlComponent中,因为作为父组件,它控制着MissionService的生命周期。
我希望这对你有所帮助。
出于性能原因,总是建议从你的可观察订阅中取消订阅,以避免内存泄漏,有不同的方法,
顺便说一下,我读了大部分的答案,我没有发现有人在谈论异步管道,推荐在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>
视情况而定。如果通过调用someObservable.subscribe(),您开始占用一些资源,当组件的生命周期结束时必须手动释放这些资源,那么您应该调用subscription .unsubscribe()来防止内存泄漏。
让我们仔细看看你的例子:
getHero()返回http.get()的结果。如果你查看angular 2的源代码,会发现http.get()创建了两个事件监听器:
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
通过调用unsubscribe(),你可以取消请求和监听器:
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
请注意,_xhr是特定于平台的,但我认为在您的情况下,可以安全地假设它是XMLHttpRequest()。
通常,这已经足够证明需要手动unsubscribe()调用了。但是根据WHATWG规范,XMLHttpRequest()一旦“完成”就会受到垃圾收集的影响,即使有事件监听器附加到它。所以我想这就是为什么angular 2官方指南省略了unsubscribe(),让GC清理侦听器。
至于第二个例子,它取决于参数的实现。从今天起,angular官方指南不再显示取消订阅参数。我再次查看了src,发现params只是一个行为主体。由于没有使用事件侦听器或计时器,也没有创建全局变量,因此省略unsubscribe()应该是安全的。
你的问题的底线是总是调用unsubscribe()来防止内存泄漏,除非你确定可观察对象的执行不会创建全局变量、添加事件侦听器、设置计时器或做任何其他导致内存泄漏的事情。
如果有疑问,请查看该可观察对象的实现。如果可观察对象已经在其unsubscribe()中写入了一些清理逻辑,这通常是构造函数返回的函数,那么你有充分的理由认真考虑调用unsubscribe()。
订阅本质上只有一个unsubscribe()函数来释放资源或取消可观察对象的执行。
在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()
}
}
我们添加了ngOnDestroy到我们的appcomponent,并在这个上调用unsubscribe方法。订阅可观察到的
如果有多个订阅:
@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()
}
}
当组件被销毁时,你通常需要取消订阅,但随着我们的发展,Angular会越来越多地处理它,例如在Angular4的新小版本中,他们有这个部分用于路由取消订阅:
你需要取消订阅吗?如在
ActivatedRoute:一站式的路线信息部分
路由和导航页面,路由器管理它的观察对象
提供并本地化订阅。订阅是
当组件被破坏时进行清理,以防止内存
泄漏,所以你不需要从路由paramMap取消订阅
可观察到的。
下面的例子也是Angular中创建一个组件并销毁它的好例子,看看组件是如何实现OnDestroy的,如果你需要onInit,你也可以在你的组件中实现它,比如实现onInit, OnDestroy
import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'my-astronaut',
template: `
<p>
{{astronaut}}: <strong>{{mission}}</strong>
<button
(click)="confirm()"
[disabled]="!announced || confirmed">
Confirm
</button>
</p>
`
})
export class AstronautComponent implements OnDestroy {
@Input() astronaut: string;
mission = '<no mission announced>';
confirmed = false;
announced = false;
subscription: Subscription;
constructor(private missionService: MissionService) {
this.subscription = missionService.missionAnnounced$.subscribe(
mission => {
this.mission = mission;
this.announced = true;
this.confirmed = false;
});
}
confirm() {
this.confirmed = true;
this.missionService.confirmMission(this.astronaut);
}
ngOnDestroy() {
// prevent memory leak when component destroyed
this.subscription.unsubscribe();
}
}