Choosing Between MatTable and Custom HTML Tables in Angular Material
Decide between MatTable and native HTML tables in Angular Material. This guide compares performance, implementation overhead, and provides a concrete DataSource example.
10 Jul 2025, 13:00 UTC

The Data Grid Dilemma
When building data-heavy dashboards in Angular, the primary decision is whether to use the MatTable component or a standard HTML <table> styled with Material CSS. The wrong choice leads to either an over-engineered implementation for simple lists or a manual, error-prone recreation of sorting and pagination for complex datasets.
The key takeaway: Use MatTable when you need built-in integration with MatPaginator and MatSort, or when handling large datasets via a DataSource. Use native HTML tables when the data structure is static, the row count is low, and you need absolute control over the DOM for accessibility or legacy CSS reasons.
Comparison of Implementation Paths
| Feature | MatTable (DataSource) | Native HTML Table |
|---|---|---|
| State Management | Built-in via MatSort/MatPaginator | Manual implementation |
| Rendering | Template-driven (matColumnDef) | Standard <tr> and <td> |
| Performance | Optimized for large lists via CDK | Browser-dependent; slow for 100+ rows |
| Styling | Strict Material Design specs | Fully flexible / Custom CSS |
| Setup Overhead | High (requires DataSource contract) | Low (standard HTML) |
Trade-offs and Constraints
The MatTable is not a simple wrapper; it is a logic-driven component. To use it effectively, you must implement a DataSource, which is a class that manages the data stream. This adds a layer of abstraction between your API response and the UI.
Constraints to consider:
- Version Alignment: Angular Material and the Component Dev Kit (CDK) must share the exact same version. A mismatch often results in
NullInjectionErrorwhen the table attempts to access required services. - DOM Structure:
MatTablerenders a specific structure that may conflict with some third-party CSS frameworks or legacy accessibility scripts that expect a standard<tbody>. - Bundle Size: Importing
MatTableModulepulls in several CDK dependencies. For a single, small table, this may be unnecessary overhead.
Implementing MatTable with a DataSource
To implement a professional data grid, avoid passing a simple array to the table. Instead, use a MatTableDataSource to enable seamless integration with sorting and paging.
Module Configuration
Run this in your feature module or app.module.ts. Ensure you have the required permissions to modify the project's dependency tree.
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
@NgModule({
imports: [
MatTableModule,
MatPaginatorModule,
MatSortModule
]
})
export class DataModule { }
Component Logic
In your TypeScript file, define the columns and initialize the data source. This example assumes Angular 15+.
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
@Component({
selector: 'app-user-table',
templateUrl: './user-table.component.html'
})
export class UserTableComponent implements AfterViewInit {
displayedColumns: string[] = ['id', 'name', 'email'];
dataSource = new MatTableDataSource([
{id: 1, name: 'Jane Doe', email: '[contact removed]'},
{id: 2, name: 'John Smith', email: '[contact removed]'},
]);
@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;
}
}
Template Definition
The template uses matColumnDef to map data keys to table columns.
<table mat-table [dataSource]>
<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>
Verification and Diagnostics
To verify the implementation is working correctly, perform the following checks:
- DOM Inspection: Open Browser DevTools and inspect the table. You should see classes like
mat-mdc-table(in newer versions) ormat-table. If you see standard<table>tags without these classes, the module was not imported correctly. - Interaction Test: Click a column header. The
mat-sort-headershould trigger a visual indicator (arrow) and reorder the rows. If the rows do not move, verify thatthis.dataSource.sort = this.sort;was called insidengAfterViewInit, notngOnInit. - Pagination Check: Change the page size in the paginator. The number of rendered
<tr mat-row>elements should update immediately.
Rollback Procedure
If MatTable introduces too much complexity or breaks existing CSS layouts, revert to a native table by:
- Removing
MatTableModule,MatPaginatorModule, andMatSortModulefrom the imports. - Replacing the
<table mat-table>and<ng-container>blocks with a standard<table> <thead> <tbody>structure. - Replacing
MatTableDataSourcewith a standard TypeScript array.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.