[RxJS] Just enough about ShareReplay
When new to Reactive programming with Angular. It is easy to fall into a performance issue, which is sending multi same http request to the backend.
In this post, we will first setup a basic Angular service - component, then try to understand the problem occurt behind the code: finally we will discuss the solution to solve the problem.
Setup
Let's say we have a service to fetch data:
// categories.service.ts import {Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {Category} from '../shared/model.ts' @Injectable({ providedIn: 'root' }) export class CategoriesService { constructor(private http: HttpClient) { } getAllCategories(): Observable<Course[]> { return this.http.get<Categories[]>('/api/categories') .pipe( map(response => response['payload']) ); } }
The logic for this CategoriesService is pretty simple, just a method 'getAllCategories'.
Now let's see the component code:
@Component({ selector: 'home', ... }) export class HomeComponent implements OnInit {
beginnerCategories$: Observable<Category[]>; advancedCategories$: Observable<Category[]>; constructor(private service: CategoriesService) {} ngOnInit() { const categories$ = this.service.getAllCategories(); this.beginnerCategories$ = categories$.pipe( map( cs => cs.filter(c => c.category === 'BEGINNER') ) ) this.advancedCategories$ = categories$.pipe( map( cs => cs.filter(c => c.category === 'ADVANCED') ) ) } }
In the component, we call 'CategoriesService' to fetch all the data, then we create two other observables, one is for 'beginner' category, another is 'advanced' category.
Then we use those observables inside our template, we use 'async' pipe here.
<category-card-list [categories]="beginnerCategories$ | async"> </category-card-list> <category-card-list [categories]="advancedCategories$ | async"> </category-card-list>
Understand the problem
After running the applciaiton, open the devtool, check the network tab. you will find there are TWO '/api/categories' made to the backend.
Why is that? We only call 'getAllCategories' once inside our component, why it issue two requests?
// component
ngOnInit() {
const categories$ = this.service.getAllCategories();
....
}
It is because how 'async' pipe works. 'async' pipe will automaticlly 'subscribe' to the observable. You can consider every time you use 'async' pipe, it will add one more subscription. Since inside template it uses two 'async' pipes, it means we call subscribe two times.
It is equivalent to this code:
ngOnInit() { this.service.getAllCategories().pipe( map(cs => cs.filter(c => c.category === 'BEGINNER')) ).subscribe( cs => this.beginnerCategories = cs ) this.service.getAllCategories().pipe( map(cs => cs.filter(c => c.category === 'ADVANCED')) ).subscribe( cs => this.advancedCategories = cs ) }
Because we didn't apply any cache to our service, it will issue two requests to the backend.
Solution
The solution is pretty simple, we just need to use 'shareReplay' inside service. It will share the same execution to the all subscribers.
signature:
shareReplay(bufferSize?: number, windowTime?: number, scheduler?I IScheduler): Observable
// categories.service.ts ... import {map, shareReplay} from 'rxjs/operators'; import {Category} from '../shared/model.ts' @Injectable({ providedIn: 'root' }) export class CategoriesService { constructor(private http: HttpClient) { } getAllCategories(): Observable<Course[]> { return this.http.get<Categories[]>('/api/categories') .pipe( map(response => response['payload']), shareReplay() ); } }
Now 'beginnerCategories$' and 'advancedCategories' shares the same execution of:
this.service.getAllCategories();
Best partices
ShareReplay is good for preventing duplicate HTTP reqeusts. Therefore, for the most of 'GET' and 'PUT' request, we can add 'shareReplay'.
//categories.service.ts @Injectable({providerIn> 'root'}) export class CategoriesService { constructor(private http: HttpClient) { } getAllCategories(): Observable<Category[]> { return this.http.get<Category[]>('/api/categories') .pipe( map(response => response['payload']), shareReplay() ); } loadCategoryById(cid: number) { return this.http.get<Category>(`/api/categories/${cid}`) .pipe( shareReplay() ); } saveCategory(cid: string, changes: Partial<Category>): Observable<any> { return this.http.put(`/api/categories/${cid}`, changes) .pipe( map(response => response['payload']), shareReplay() ); } }
Summary
In this post, we have see 'async' pipe might casues multi http request, and to prevent thsi problem we can use 'shareReplay' inside our service to prevent the problem.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2019-03-05 [Algorithm] How to use Max Heap to maintain K smallest items
2019-03-05 [HTML5] Avoiding CSS Conflicts via Shadow DOM CSS encapsulation
2019-03-05 [React] Simplify and Convert a Traditional React Form to Formik
2019-03-05 [Javascript Crocks] Make your own functions safer by lifting them into a Maybe context
2019-03-05 [Javascript Crocks] Compose Functions for Reusability with the Maybe Type
2019-03-05 [Algorithm] Array production problem
2019-03-05 [Algorithm -- Dynamic Programming] Recursive Staircase Problem