如何在多个组件之间重用Angular可观察对象?(避免重复请求)

5
我是一名有用的助手,可以为您翻译文本。
我有一个名为"MyHttpService"的服务,其中包含一个可观察对象,如下所示:
grabData() {
return this.http.get('myaddress')
                         .map((res:Response) => {return res.json()})
                         .catch((error:any) => Observable.throw(error.json().error || 'Server error')); 
}

我有两个组件:OneComponent 和 TwoComponent。这两个组件都注入了 "MyHttpService",并且依赖于 MyHttpService 返回的数据。
OneComponent 先加载,TwoComponent 在按钮点击后加载。
在每个组件的 onNgInit() 中,我有以下代码:
     this.myHttpService.grabData()
        .subscribe(
              data => {
 // do something to the data
});

假设我在两个组件中都有订阅,那么是否正确假设不会调用多个HTTP请求,并且当TwoComponent加载时对“grabData()”的调用是OneComponent已经拉取的相同数据?或者它会发起新的调用?我希望避免对同一端点进行多个HTTP请求。如果每次调用具有此内容的组件时都会进行多个调用,那么处理这种情况的最佳方法是什么,以便我不必在初始化TwoComponent时多次调用服务?
3个回答

11

Http 服务返回的是所谓的cold observables。这意味着每个新的订阅者都会导致该 observable 的工作再次执行,对于 Http 来说,就是发起网络请求。

幸运的是,在 RxJS 中有机制允许你将 observable 进行发布(也称为多路复用或组播),以便多个订阅者不会导致多个请求。

我通常使用 .publishReplay(1).refCount() 这一对操作符来实现对 Http 的发布。其中 .publishReplay(1) 表示后续的订阅者可以立即获得最近成功的值,而无需发出另一个请求。 .refCount() 则表示第一个订阅者发起请求,最后一个取消订阅者清除原始的 Http observable。

版本5.4.0添加了这两个操作符的快捷方式,即 .shareReplay(1)

只需在您的grabData()服务方法中加入任何一个版本即可达到所需效果。

grabData() {
    return this.http.get('myaddress')
                    .map((res:Response) => {return res.json()})
                    .catch((error:any) => 
                         Observable.throw(error.json().error || 'Server error'))
                    .publishReplay(1).refCount(); // or .shareReplay(1)
}

而最后一个取消订阅的人会清理原始的 Http observable。- 对,所以如果进行了其他订阅,则会再次执行 http 请求,对吗?因此基本上,直到至少有一个订阅者,http 请求结果将被共享,然后才会重复请求。在这里,AsyncSubject 似乎是更好的选择,你认为呢? - Max Koretskyi
1
@Maximux,你能分享一下AsyncSubject方法的含义吗?根据我使用上述方法时看到的情况,当我点击按钮时,客户端会使用相同的服务“grabData()”进行第二次请求,而不是使用共享/第一次请求的“缓存”值。 - Rolando

4
您可以使用share操作符将可观测对象设为多播。在单个组件中也很有用,因为对同一可观测对象进行多个异步绑定会导致多次请求。这里有一个很好的ng-conf演讲(点击此处)涵盖了这个问题。
import 'rxjs/add/operator/share';

grabData() {
  return this.http.get('myaddress')
                  .map((res:Response) => {return res.json()})
                  .catch((error:any) => Observable.throw(error.json().error || 'Server error'))
                  .share(); 
}

我在共享和动态模块方面遇到了问题,当我在动态模块之间导航时,在某些调用中我会失去与共享运算符的订阅。 - Ernesto Alfonso

3
当你在TwoComponent中订阅时,会导致HTTP请求再次发送。为了防止多次HTTP调用,最好的方法是将响应数据存储在你的服务中,在HTTP调用之前,检查是否已经保存了数据,如果是,则直接返回数据。

2
我不想打负面评价,但是这种使用 RxJS 的方式并不符合惯用法,并且无法处理在网络请求返回之前进行第二个订阅的情况。我遵循的建议是尽量减少或消除在服务中手动订阅 observables 的操作,最好在组件中也是如此。相反,使用操作符组合 observables 并使用“异步”管道在视图中消费它们。 - GregL
1
@GregL 我需要更深入地了解 RxJS,但我已经为你的答案点赞了。我理解你对我建议的方式所持有的担忧,如果有一种直接通过 RxJS 实现的方法,那肯定是更好的选择。 - SimplyComplexable
我发现shareshareReplay存在几个问题,都与动态模块有关,我认为这可能是一个可行的解决方案。 - Ernesto Alfonso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接