使用Async Pipe时避免后端Observable重复请求的解决方案
解决Angular模板中
getFoos方法导致后端重复请求的问题 问题根源
- Angular的变更检测机制会频繁调用模板中的
getFoos方法,每次调用都会生成新的Observable实例,引发重复订阅。 - 当前逻辑中,获取到新
Foo后调用obsFoos.next会触发Foos$推送新值,进而让所有订阅getFoos返回Observable的地方重新执行逻辑,形成请求循环。 - 缺少对已请求ID的缓存,导致同一
someId被多次触发请求。
解决方案
通过三层缓存+优化变更检测策略,彻底解决重复请求问题:
1. 添加缓存逻辑
在组件中新增三个缓存容器,分别存储已加载的Foo、正在进行的请求、以及每个(rowIndex, matSelectIndex)对应的Observable:
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { BehaviorSubject, Observable, of, iif, switchMap, tap, map, shareReplay, catchError } from 'rxjs'; import { Foo, KsFoosService } from './your-services-path'; @Component({ selector: 'your-component', templateUrl: './your-component.html', // 开启OnPush变更检测,减少不必要的方法调用 changeDetection: ChangeDetectionStrategy.OnPush }) export class YourComponent { private obsFoos: BehaviorSubject<Foo[]> = new BehaviorSubject<Foo[]>([]); public Foos$: Observable<Foo[]> = this.obsFoos.asObservable(); public otherFoos$: Observable<Foo[]> = of([]); // 替换成你的otherFoos$逻辑 // 缓存已加载的Foo,key为someId private fooCache = new Map<string, Foo>(); // 缓存正在进行的请求,防止同一ID并发请求 private pendingRequests = new Map<string, Observable<Foo>>(); // 缓存每个(rowIndex, matSelectIndex)对应的Observable,避免重复创建 private fooObservables = new Map<string, Observable<Foo[]>>(); constructor(private _ksFoosService: KsFoosService) {} private getId(rowIndex: number, selectionIndex: number): string { // 替换成你的getId逻辑 return `${rowIndex}-${selectionIndex}`; } public getFoos(rowIndex: number, selectionIndex: number): Observable<Foo[]> { const key = `${rowIndex}-${selectionIndex}`; // 1. 若已有缓存的Observable,直接返回 if (this.fooObservables.has(key)) { return this.fooObservables.get(key)!; } const someId = this.getId(rowIndex, selectionIndex); if (!someId) { const obs = this.otherFoos$; this.fooObservables.set(key, obs); return obs; } // 2. 若已加载过该someId的Foo,直接返回缓存值 if (this.fooCache.has(someId)) { const currentFoos = this.obsFoos.value; const obs = of([...currentFoos, this.fooCache.get(someId)!]); this.fooObservables.set(key, obs); return obs; } // 3. 若有正在进行的请求,复用该请求 if (this.pendingRequests.has(someId)) { const obs = this.pendingRequests.get(someId)!.pipe( map(res => [...this.obsFoos.value, res]) ); this.fooObservables.set(key, obs); return obs; } // 4. 发起新请求并缓存结果 const request$ = this._ksFoosService.getById(someId).pipe( tap(res => { this.fooCache.set(someId, res); const currentFoos = this.obsFoos.value; // 仅当Foo不在当前数组时更新全局列表 if (!currentFoos.some(x => x.id === someId)) { this.obsFoos.next([...currentFoos, res]); } this.pendingRequests.delete(someId); }), map(res => [...this.obsFoos.value, res]), catchError(() => { this.pendingRequests.delete(someId); return this.otherFoos$; }), shareReplay(1) // 缓存请求结果,后续订阅直接取缓存 ); this.pendingRequests.set(someId, request$); this.fooObservables.set(key, request$); return request$; } }
2. 模板保持原结构
无需修改模板代码,因为缓存逻辑已经确保每个(rowIndex, matSelectIndex)组合只会生成一次Observable,且请求结果被复用:
<mat-option *ngFor="let foo of getFoos(rowIndex, matSelectIndex) | async" [ngClass]="{'inactiveFoo': !foo?.status}" [value]="foo.id"> <span [innerText]="foo.name"></span> </mat-option>
关键优化点说明
fooCache:存储已加载的Foo,避免同一ID重复发起请求。pendingRequests:处理并发场景,防止同一ID同时发起多个请求。fooObservables:缓存每个行/选择框组合的Observable,避免变更检测时重复创建实例。shareReplay(1):缓存请求结果,后续订阅直接获取缓存值,不重新发起请求。ChangeDetectionStrategy.OnPush:减少不必要的变更检测触发,降低getFoos方法的调用频率。
内容的提问来源于stack exchange,提问作者Ahmed Aljazara
相关产品推荐
相关产品推荐

