Implementing Client-Side Pagination and Sorting with MatTableDataSource
Learn how to integrate MatTableDataSource with MatPaginator and MatSort to handle client-side data management in Angular Material tables, including critical lifecycle timing and memory limits.
14 Nov 2025, 20:45 UTC

Solving Data Management in Angular Material Tables
Managing large arrays of data in a UI often leads to performance lag or complex manual logic for slicing arrays and tracking indices. The MatTableDataSource class solves this by acting as a data wrapper that integrates directly with Angular Material's paginator and sort components. Instead of writing custom logic to handle slice() or sort() operations on your arrays, you delegate these tasks to the data source wrapper.
The Integration Mechanism
To enable pagination and sorting, you must link the MatTableDataSource instance to the MatPaginator and MatSort directives. Because these directives rely on DOM elements, the connection must occur after the view has initialized, specifically within the ngAfterViewInit lifecycle hook.
Worked Implementation Example
This example assumes you are using Angular Material 15+ and have imported MatTableModule, MatPaginatorModule, and MatSortModule in your component or module.
// user-table.component.ts
import { Component, ViewChild, AfterViewInit } from '@angular';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
export interface UserData {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-table',
templateUrl: './user-table.component.html'
})
export class UserTableComponent implements AfterViewInit {
displayedColumns: string[] = ['id', 'name', 'email'];
// Initialize the data source with a raw array
dataSource = new MatTableDataSource<UserData>([
{id: 1, name: 'Alice', email: '[contact removed]'},
{id: 2, name: 'Bob', email: '[contact removed]'},
{id: 3, name: 'Charlie', email: '[contact removed]'},
{id: 4, name: 'David', email: '[contact removed]'},
{id: 5, name: 'Eve', email: '[contact removed]'},
]);
// Access the DOM elements via ViewChild
@ViewChild(MatPaginator) paginator! : MatPaginator;
@ViewChild(MatSort) sort! : MatSort;
ngAfterViewInit() {
// Link the paginator and sort to the data source
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
}
In the corresponding HTML, the mat-sort-header directive must be applied to the header cells, and the matColumnDef must match the keys of the data object exactly.
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
<td mat-cell *matCellDef="let element"> {{element.id}} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Email </th>
<td mat-cell *matCellDef="let element"> {{element.email}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>
Critical Limitations and Common Pitfalls
- Memory Constraints:
MatTableDataSourceperforms client-side operations. This means the entire dataset must be loaded into the browser's memory. For datasets exceeding a few thousand rows, this will cause UI freezing. In such cases, you must implement a customDataSourceclass that handles API requests for specific page indices (server-side pagination). - Lifecycle Timing: A common error is attempting to assign
this.dataSource.paginator = this.paginatorinsidengOnInit. Because@ViewChildqueries are not resolved until the view is rendered,this.paginatorwill beundefined, and pagination will fail to initialize. - Case Sensitivity: The
matColumnDefstring must be an exact match for the property name in your data object (e.g.,'email'vs'Email'). If they differ, the sorting logic will fail to identify the property to sort by, and cells will render empty.
Verification and Testing
To verify the implementation is functioning correctly, perform the following checks:
- Pagination Check: Change the page size in the paginator dropdown. The number of visible rows should update immediately without a page reload.
- Sort Check: Click a column header. The arrow indicator should appear, and the row order should change. Click again to toggle between ascending and descending.
- Data Refresh: Update the
this.dataSource.dataarray programmatically. The table should automatically refresh and reset the pagination index to zero.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.