我遇到一个场景,订阅将触发2倍。但是,如果我通过将poller方法复制粘贴到组件中来摆脱服务,那么问题就消失了。
我正在运行Ionic 3,但我认为这并不重要。 “ rxjs”:“ 5.4.3”
在unsubscribe()
上调用 ngDestroy()
my-service.service.ts
public pollingDataReceived = new Subject<any>();
pollForData = (id: string) : Subscription => {
let stopPollingForStatus = false;
return Observable.interval(environment['pollingInterval'])
.takeWhile(() => !stopPollingForStatus)
.concatMap(() => {
return this.getAnalysisById(id);
})
.subscribe((analysisData) => {
if (analysisData) {
if (analysisData.status === 'questions') {
stopPollingForStatus = false;
}
else {
stopPollingForStatus = true;
const result = {
someData: 'whatever'
};
console.log('raise pollingDataReceived event');// This happens ONCE
this.pollingDataReceived.next(result);
}
}
else {
alert('no analysis data!');
}
});
}
my.component.ts (订阅者)
private pollingData: Subscription;
someEventHandler() {
this.pollingData = this.pollForData(this.id);
}
ngOnInit(): void {
this.myService.pollingDataReceived.subscribe((data) => {
this.analysis = data.analysisData;
//This is getting called 2x. Maybe I double subscribed?
myNavigator.navigateToPageByStatus(myPage);
}, (err) => { console.log(err); });
}
ngOnDestroy() {
console.log('ngOnDestroy: unsubscribe pollingData in flow');// This does get called
this.pollingData.unsubscribe();
}
答案 0 :(得分:2)
除非在组件级别提供,否则Angular Services的行为类似于Singleton。因此,pollingDataReceived
主题将继续保留其订阅,直到该服务生效为止。
对于您而言,先前访问this.myService.pollingDataReceived
的陈旧订阅也将被解雇。因此,订阅者在 revisit 上触发两次。
在ngDestroy
上进行类似于pollingData
的清理将解决此问题。
答案 1 :(得分:1)
处理程序触发两次的原因-是当您多次推送至该主题时,您永远不会退订服务pollingDataReceived
主题。您的服务可能只创建一次,而您的组件在重新访问页面时被重新创建了多次。组件被销毁,尽管它们对同一主题的订阅保持不变。
最简单的解决方法是在组件ngOnDestroy
方法中以@ashish.gd suggested的形式退订该主题。
每次创建新主题(as suggested here)可能会导致内存泄漏。由于您的旧订阅将继续收听旧主题。提防。
虽然最简单的方法是退订pollingDataReceived
主题,但更好的方法是让服务pollForData()
返回一个Observable。
除非您需要多个使用者阅读相同 pollingDataReceived
主题,否则您无需订阅。以下是反映该想法的示例代码:
pollForData = (id: string) : Observable => {
return Observable.interval(environment['pollingInterval'])
.concatMap(() => this.getAnalysisById(id))
.takeWhile(data => data.status === 'questions')
// other handling logic goes here
// .map(), .switchMap(), etc
// NOTE: no subscription here
}
在控制器中您可以执行类似的操作
// just an utility subject to track component destruction
onDestroy$ = new Subject();
someEventHandler() {
this.pollForData(this.id).pipe(
// this will ensure that subscription will be completed on component destruction
takeUntil(this.onDestroy$)
).subscribe((data) => {
this.analysis = data.analysisData;
myNavigator.navigateToPageByStatus(myPage);
}, (err) => { console.log(err); });
}
ngOnDestroy() {
this.onDestroy$.next(void 0);
}
-
注意1 :目前看来您的服务是在模块级别提供的,这意味着如果您有两个使用者同时请求pollForData(id)
(例如,两个组件一页)-这两个请求都将“写入” 同一主题。容易出错的行为。
注意2 :当前在您的代码takeWhile
中的谓词将不会运行,直到您在流中收到 new 事件
Observable.interval(environment['pollingInterval'])
.takeWhile(() => !stopPollingForStatus)
这意味着environment['pollingInterval']
时间内,您在内存中将有不必要的订阅。这并不重要,但根据时间安排可能会很麻烦。
-
希望这对您有所帮助,编码愉快!
答案 2 :(得分:1)
使用完成主题,直到
private pollingData: Subscription;
private finalise = new Subject<void>();
someEventHandler() {
this.pollingData = this.pollForData(this.id);
}
ngOnInit(): void {
this.myService.pollingDataReceived.pipe(
takeUntil(finalise)
).subscribe((data) => {
this.analysis = data.analysisData;
//This is getting called 2x. Maybe I double subscribed?
myNavigator.navigateToPageByStatus(myPage);
}, (err) => { console.log(err); });
}
ngOnDestroy() {
this.finalise.next();
this.finalise.complete();
}
答案 3 :(得分:0)
我遇到了类似的问题,请尝试以下代码,这只是解决潜在问题的一种解决方法。
this.pollingDataReceived.next(result);
this.pollingDataReceived = new BehaviorSubject<any>(); // Here, create new object with null value
答案 4 :(得分:0)
还有另一种方法可以实现这一目标。您可以使用ReplaySubject
中的rxjs
。
class pollingComponent {
private destroyed$: ReplaySubject<boolean> = new ReplaySubject(1);
constructor(
private pollingService: Service) {}
ngOnInit() {
this.pollingService
.takeUntil(this.destroyed$)
.subscribe(...);
}
ngOnDestroy() {
this.destroyed$.next(true);
this.destroyed$.complete();
}
}
基本上,建议避免使用香草Subject
。您可以详细了解差异here
答案 5 :(得分:0)
如果您需要在访问其他组件时保持订阅事件(例如消息传递),您可以通过设置条件来检查事件是否没有观察者,然后才订阅它,从而仅订阅一次。 我相信这不是最佳实践,但确实可以。
它看起来像这样:
ngOnInit(): void {
if (this.myService.pollingDataReceived.observers.length === 0) {
this.myService.pollingDataReceived.subscribe((data) => {
this.analysis = data.analysisData;
myNavigator.navigateToPageByStatus(myPage);
}, (err) => { console.log(err); });
}
}