MatTable Virtual Scrolling & MatSort: Preventing Concurrent Data Requests
0 reputation · 23 Nov 2022, 10:12 UTC
0 reputation · 23 Nov 2022, 10:12 UTC
When a MatTable is combined with MatSort and virtual scrolling, each viewport shift triggers the data source’s connect method, which may issue a new HTTP GET. Rapid changes in page size or sort order can therefore spawn multiple concurrent requests before the previous ones complete. This can lead to visual inconsistencies, server rate‑limit errors, and an indeterminate loading state.
The Angular Material library currently offers no built‑in debounce or mergeMap strategy for the data source. Developers must decide whether to implement a custom RxJS pipeline (e.g., mergeMap(..., 1) or switchMap) or rely on the default behavior, which may produce an unpredictable user experience across applications.
What is the most reliable approach for managing concurrent data loads when virtual scrolling and sorting are used together? Should Angular Material provide a default concurrency control strategy, or should this responsibility remain with the application layer? How can one evaluate the trade‑offs between request coalescing and responsiveness?
33275 reputation · 23 Nov 2022, 19:14 UTC
When a MatTable is combined with MatSort and CDK virtual scrolling, each viewport change can trigger a new HTTP request. The most reliable way to avoid overlapping calls is to let the application layer cancel the previous request as soon as a new sort or scroll event arrives. In practice this is achieved with switchMap (or mergeMap(..., 1)) inside the DataSource.connect() stream, optionally preceded by a small debounceTime to coalesce rapid user actions. Angular Material itself does not provide a default concurrency strategy because its DataSource API is intentionally minimal and meant to be adapted to the specific server contract of the app.
switchMap is the safest choiceHttpClient.If the backend has strict rate limits or you want to reduce the number of round‑trips during fast scrolling, add a debounceTime(200) before switchMap. The trade‑off is a brief visual lag: the table will show a loading indicator until the debounced request completes.
export class VirtualScrollDataSource extends DataSource<MyItem> {
private readonly pageSize$ = new BehaviorSubject<number>(50);
private readonly sort$ = new BehaviorSubject<Sort>({active: '', direction: ''});
private readonly scroll$ = new Subject<number>(); // index of first visible row
constructor(private readonly api: MyApiService) {
super();
}
connect(): Observable<MyItem[]> {
return combineLatest([this.pageSize$, this.sort$, this.scroll$]).pipe(
debounceTime(200), // optional coalescing
switchMap(([size, sort, index]) => {
this.loading = true;
return this.api.fetchPage({size, sort, offset: index}).pipe(
tap(() => this.loading = false),
catchError(err => {
this.loading = false;
return of([]); // or a fallback
})
);
})
);
}
disconnect() {}
}
switchMap alone gives instant feedback; adding debounceTime introduces a ~200 ms delay.switchMap alone can send many requests if the user scrolls extremely fast.loading flag so the UI shows a spinner or placeholder until the latest data arrives, avoiding flicker from stale rows.AbortSignal or use a short‑lived token.Because DataSource is an extensible interface, a one‑size‑fits‑all concurrency policy would constrain developers who need custom caching, optimistic updates, or server‑specific cancellation semantics. Therefore, keeping concurrency control in the application layer preserves flexibility while giving developers a clear pattern (switchMap + optional debounce).
MatSort and scroll events into observables (e.g., sort.sortChange, cdkVirtualScrollViewport.scrolledIndexChange). With this pattern you get deterministic, cancel‑safe data loading that scales from simple tables to complex, high‑volume grids.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.