Choosing MatTable, MatSort, and MatPaginator for Angular Material Tables
Learn how to combine MatTable, MatSort, and MatPaginator with MatTableDataSource for small client‑side datasets while handling large or remote data manually.
10 Jul 2025, 18:33 UTC

Problem: sortable, paginated rows with inconsistent behavior
A typical Angular table needs sorting, paging and keyboard accessibility. When developers write custom markup they often end up with duplicated logic, mismatched keyboard handling and missing ARIA roles, which harms usability and accessibility.
Why Angular Material simplifies the task
The Angular Material library provides three dedicated components that handle the UI and interaction for you:
- MatTable – provides a semantic
<table>structure with Material styling. - MatSort – adds sortable column headers and manages sort state.
- MatPaginator – supplies page navigation, page‑size controls and updates the visible range.
None of these components decides where the data originates; they merely render what you give them. The guidance assumes Angular Material 15 or newer, which introduced the current API shapes.
Client‑side approach for modest datasets
When all rows comfortably fit in the browser memory, you can use MatTableDataSource as a convenience layer. The steps are:
- Import the required modules in your Angular
NgModule: - Define column headers with
matColumnDefand create aMatTableDataSourcefrom your array: - Bind the source to the table and attach the sort and paginator instances after the view is initialized:
import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
const rows = [...]; // your data array
const dataSource = new MatTableDataSource(rows);
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatPaginator) paginator!: MatPaginator;
ngAfterViewInit() {
dataSource.sort = this.sort;
dataSource.paginator = this.paginator;
}
With this setup the header click toggles sort order, the paginator updates the visible slice, and keyboard navigation works out of the box because the components expose the correct ARIA attributes.
Trade‑off and limitation
The client‑side path is simple but has a clear limitation: sorting and paging operate only on the rows already loaded into MatTableDataSource. If the dataset grows beyond a few thousand items, the browser must sort or filter the entire array, which can become sluggish. For remote or very large collections you must abandon the convenience source and handle pagination, sorting and filtering yourself, reacting to MatSortChange and MatPaginatorPageEvent events and sending the requested parameters to your backend.
Worked example
Below is a minimal component that demonstrates the client‑side flow. The code is illustrative; you should adapt it to your own data shape.
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { MatPaginator } from '@angular/material/paginator';
interface Row {
id: number;
name: string;
age: number;
}
@Component({
selector: 'app-simple-table',
template: `
#
{{row.id}}
Name
{{row.name}}
Age
{{row.age}}
`
})
export class SimpleTableComponent implements AfterViewInit {
displayedColumns = ['id', 'name', 'age'];
dataSource = new MatTableDataSource([]);
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatPaginator) paginator!: MatPaginator;
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
}
}
To verify the behavior, load roughly 25 rows, click a column header to change sort direction, and navigate through the paginator. Use a screen reader to confirm that column labels announce sort state and current page.
Actionable closing rule
Prototype the Material table for a small, client‑side dataset, confirm that sorting, paging and keyboard accessibility work, then define the responsibilities for sort, page and loading events before reusing the same UI for server‑backed data. This prevents duplicated logic and ensures a consistent user experience across both scenarios.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.