Choosing Between Client-Side and Server-Side Pagination in Angular Material
A decision guide for Angular Material pagination, comparing client-side MatTableDataSource with server-side API integration for performance and scalability.
10 Oct 2025, 02:18 UTC

The Pagination Decision: Memory vs. Network
When building a data table with Angular Material, the key engineering decision is where the data slicing happens. Choosing the wrong strategy leads to either a sluggish browser that struggles with large in-memory arrays or an unnecessarily complex API integration for a small, static list.
The goal is a responsive interface that lets users navigate thousands of records without loading the entire dataset into browser memory. Both strategies use the same MatPaginator UI component; they differ in how the data behind it is fetched and sliced.
Comparison of Pagination Strategies
The following table compares the two supported patterns for MatTable and MatPaginator integration.
| Feature | Client-Side (MatTableDataSource) | Server-Side (Manual Integration) |
|---|---|---|
| Data Volume | Small to Medium (< 2,000 rows) | Large or unbounded |
| Initial Load | Slow (fetches all data) | Fast (fetches one page) |
| Interaction | Instant page switching | Network latency per page |
| Implementation | Low complexity (built-in) | Medium complexity (custom API) |
| Memory Usage | High (proportional to total set) | Low (proportional to page size) |
Trade-offs and Constraints
Client-Side Pagination
This approach uses MatTableDataSource, a utility class that wraps your data array and handles slicing, sorting, and filtering internally based on the MatPaginator state. It suits configuration lists, settings pages, or small datasets where the full payload is modest. Beyond a few thousand rows, memory usage and initial load time degrade noticeably.
Server-Side Pagination
Here, the MatPaginator acts purely as a UI trigger. Each page change sends a new HTTP request with offset (starting record) and limit (page size) parameters. This requires a backend that supports those parameters and returns the total record count alongside each page of data. It is the standard choice for large datasets.
Implementation: Server-Side Pagination
Client-side pagination is a simple property assignment, so this example focuses on the more complex server-side pattern. Assumes Angular Material 15+ with standalone or NgModule-based setup.
Component Configuration
Ensure Angular Material is installed in your project (run in your project root, requires Angular CLI):
ng add @angular/materialIn your component, use @ViewChild to access the paginator. The paginator reference is only available after the view initializes, so guard against using it too early.
// data-table.component.ts
import { Component, ViewChild, OnInit } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { DataService } from './data.service';
@Component({
selector: 'app-data-table',
templateUrl: './data-table.component.html'
})
export class DataTableComponent implements OnInit {
@ViewChild(MatPaginator) paginator!: MatPaginator;
dataSource: any[] = [];
totalRecords = 0;
pageSize = 10;
isLoading = false;
constructor(private dataService: DataService) {}
ngOnInit() {
this.loadData(0, this.pageSize);
}
onPageChange(event: PageEvent) {
this.loadData(event.pageIndex, event.pageSize);
}
loadData(pageIndex: number, pageSize: number) {
this.isLoading = true;
this.dataService.getData(pageIndex, pageSize).subscribe(res => {
this.dataSource = res.items; // one page of data
this.totalRecords = res.total; // total count from the server
this.isLoading = false;
});
}
}Template Integration
The [length] property must be bound to the total count from the server, not the length of the current page array. The (page) event drives each new fetch.
<table mat-table [dataSource]="dataSource">
<!-- column definitions here -->
</table>
<mat-paginator
[length]="totalRecords"
[pageSize]="pageSize"
[pageSizeOptions]="[5, 10, 25, 100]"
(page)="onPageChange($event)">
</mat-paginator>Verification and Diagnostics
- Network inspection: Open browser DevTools, Network tab. Navigate to page 2. You should see one new XHR request with updated query parameters (for example,
?offset=10&limit=10). If no request fires, the(page)event binding is broken. - Length validation: The paginator label should read something like "1–10 of 500" where 500 is the database total. If it shows "1–10 of 10", you are binding
[length]to the current page array instead of the server total. - Page size changes: Switching the page-size dropdown should trigger a new API call returning the correct number of records.
Limitations
Server-side pagination adds a network round-trip per page change, so you need a loading indicator to prevent interaction with stale data. Sorting and filtering must also be implemented server-side in this mode — MatTableDataSource conveniences no longer apply. Client-side pagination, conversely, cannot scale past a few thousand rows without memory and initial-load penalties. Verify behavior against your actual Angular Material version, as APIs evolve between major releases.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.