Learn how to build responsive, feature-rich data tables using Angular Material’s powerful <mat-table> component. This step-by-step tutorial will guide you through creating dynamic Angular tables with sorting, pagination, filtering, selection, and more—all powered by Material UI and Angular.
Angular Material Table is a must-have component when working with tabular data in modern web apps, especially for admin dashboards or CRUD interfaces. In this guide, you’ll build real-world examples to fully master the mat-table and its capabilities.
Let's get started with the main step!
Step 1: Preparation
We’ll build all the Angular Material Table examples in a single Angular project. Let’s start by setting up the Angular application, installing Angular Material, and configuring the required modules.
Requirements
- Node.js and NPM (latest LTS recommended)
- Angular CLI
- Angular Material
- REST API for data (used in later steps)
- Text Editor or IDE (VSCode recommended)
- Terminal or Command Prompt
Create a New Angular App
Open your terminal and run the following command to install or update Angular CLI:
sudo npm install -g @angular/cli
Now, create a new Angular project:
ng new material-table
Use the default options when prompted. After the project is created, navigate into the folder and serve the app:
cd material-table
ng serve --open
That command will create a new Angular app with the name `material-table` and pass all questions as the default, and then the Angular CLI will automatically install the required NPM modules. After finishing, go to the newly created Angular folder and then run the Angular app for the first time.
cd ./material-table
ng serve --open
This command opens the default Angular app in your browser.
Install Angular Material
Next, install Angular Material and its dependencies using the Angular CLI schematics:
ng add @angular/material
Press Enter to accept all default configurations (theme, animations, etc.). Then, open the project in your text editor.
To use mat-table, import MatTableModule in your app.module.ts (or directly in the component if using standalone components):
import { MatTableModule } from '@angular/material/table';
Step 2: Basic Table Example
We’ll start with a simple table that displays a static list of players with their names and teams.
Create a New Component
Generate a new component using Angular CLI:
ng g c components/basic-table
If using Angular standalone components, update basic-table.component.ts like this:
import { Component } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
import { Player } from '../../player';
@Component({
selector: 'app-basic-table',
standalone: true,
imports: [MatTableModule],
templateUrl: './basic-table.component.html',
styleUrl: './basic-table.component.scss'
})
export class BasicTableComponent {
displayedColumns: string[] = ['name', 'team'];
dataSource = Player;
}
Create the Data Source
Create a new file src/app/player.ts with the following sample data:
export const Player = [
{ id: 1, name: 'Robbie Fowler', team: 'Liverpool', photo: 'photo1.jpeg', lat: 53.430855, lng: -2.960833 },
{ id: 2, name: 'Paul Ince', team: 'Manchester United', photo: 'photo2.jpeg', lat: 53.463097, lng: -2.291351 },
{ id: 3, name: 'Eric Cantona', team: 'Manchester United', photo: 'photo3.jpeg', lat: 53.463097, lng: -2.291351 },
{ id: 4, name: 'Thierry Henry', team: 'Arsenal', photo: 'photo4.jpeg', lat: 51.554902, lng: -0.108449 },
{ id: 5, name: 'Alan Shearer', team: 'Newcastle United', photo: 'photo5.jpeg', lat: 54.975593, lng: -1.621678 },
{ id: 6, name: 'Dennis Bergkamp', team: 'Arsenal', photo: 'photo6.jpeg', lat: 51.554902, lng: -0.108449 },
{ id: 7, name: 'Didier Drogba', team: 'Chelsea', photo: 'photo7.jpeg', lat: 51.481683, lng: -0.190956 },
{ id: 8, name: 'Jurgen Klinsmann', team: 'Tottenham Hotspur', photo: 'photo8.jpeg', lat: 51.604320, lng: -0.066395 },
{ id: 9, name: 'Robin Van Persie', team: 'Arsenal', photo: 'photo9.jpeg', lat: 51.554902, lng: -0.108449 },
{ id: 10, name: 'David Beckham', team: 'Manchester United', photo: 'photo10.jpeg', lat: 53.463097, lng: -2.291351 },
{ id: 11, name: 'Steven Gerrard', team: 'Liverpool', photo: 'photo11.jpeg', lat: 53.430855, lng: -2.960833 },
{ id: 12, name: 'Ian Rush', team: 'Liverpool', photo: 'photo12.jpeg', lat: 53.430855, lng: -2.960833 },
];
Create the HTML Table Template
Update basic-table.component.html with:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Position Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let element"> {{element.team}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
In basic-table.component.scss, add:
table {
width: 100%;
}
Add Routing and Navigation
Update app.routes.ts:
import { BasicTableComponent } from './components/basic-table/basic-table.component';
export const routes: Routes = [
{ path: 'basic-table', component: BasicTableComponent },
];
In app.component.html:
<div class="main">
<h1>Angular Table</h1>
<ul>
<li><a routerLink="/basic-table" routerLinkActive="active">Basic Table</a></li>
</ul>
<router-outlet />
</div>
In app.component.ts:
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink, RouterLinkActive],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'material-table';
}
In app.component.scss:
.main {
margin: 20px;
padding: 20px;
}
Now run the app again:
ng serve --open
You should see the basic Angular Material table in action:
Step 3: REST API Datasource Example
In this step, we'll replace the static data with a dynamic REST API source. This is a common requirement when working with backend databases or remote data in Angular apps.
We’ll simulate fetching player data from a REST API using Angular’s HttpClient and a local JSON file served via a fake backend.
Set Up Angular HTTP Client
First, import the HttpClientModule into your root module or standalone component. If you're using standalone components, import them directly into the component.
If you're using app.module.ts, add:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
HttpClientModule,
// other modules
]
})
export class AppModule {}
Or in a standalone component, update src/app/app.config.ts:
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync(), provideHttpClient()]
};
Create a Mock REST API with JSON Server
We’ll use [JSON Server](https://github.com/typicode/json-server) to mock a REST API:
1. Install JSON Server globally (or locally if preferred):
npm install -g json-server
2. Create a db.json file in the root of your project with this content:
{
"players": [
{
"id": 1,
"name": "Robbie Fowler",
"team": "Liverpool"
},
{
"id": 2,
"name": "Paul Ince",
"team": "Manchester United"
},
{
"id": 3,
"name": "Eric Cantona",
"team": "Manchester United"
},
{
"id": 4,
"name": "Thierry Henry",
"team": "Arsenal"
},
{
"id": 5,
"name": "Alan Shearer",
"team": "Newcastle United"
}
]
}
3. Run the server in the new Terminal tab:
json-server --watch db.json
The API will now be accessible at http://localhost:3000/players.
Create REST API Table Component
Generate a new component to display the table using API data:
ng g c components/remote-data-table
Update remote-data-table.component.ts with the following code:
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
@Component({
selector: 'app-remote-data-table',
imports: [MatTableModule],
templateUrl: './remote-data-table.component.html',
styleUrl: './remote-data-table.component.scss'
})
export class RemoteDataTableComponent {
displayedColumns: string[] = ['name', 'team'];
dataSource: any[] = [];
constructor(private readonly http: HttpClient) { }
ngOnInit(): void {
this.http.get<any[]>('http://localhost:3000/players')
.subscribe(data => {
this.dataSource = data;
});
}
}
Update the HTML template in remote-data-table.component.html:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let element"> {{element.team}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
Add styles in remote-data-table.component.scss:
table {
width: 100%;
}
Add Navigation to REST API Table
Update app.routes.ts:
import { Routes } from '@angular/router';
import { BasicTableComponent } from './components/basic-table/basic-table.component';
import { RemoteDataTableComponent } from './components/remote-data-table/remote-data-table.component';
export const routes: Routes = [
{ path: 'basic-table', component: BasicTableComponent },
{ path: 'remote-data-table', component: RemoteDataTableComponent },
];
In app.component.html, add a link to the new component:
<div class="main">
<h1>Angular Table</h1>
<ul>
<li><a routerLink="/basic-table" routerLinkActive="active">Basic Table</a></li>
<li><a routerLink="/remote-data-table" routerLinkActive="active">REST API Table</a></li>
</ul>
<router-outlet />
</div>
Final Result
Once the app is running (ng serve --open) in the previous terminal tab and the JSON server is active, navigate to http://localhost:4200/api-table to see the table fetching live data from the mock REST API.
Step 4: Row Templates Example (Custom Cell Templates)
Angular Material <mat-table> allows you to customize each row or cell using Angular templates. This is useful when you want to display complex content like images, buttons, maps, or styled elements inside a table cell.
In this example, we’ll customize the table row to show a player’s name, team, and photo using a template in each cell.
Generate a New Component
Generate a new Angular component for row templates:
ng g c components/row-template
Define the Data Source
Re-use the same Player data from earlier (src/app/player.ts), or copy it into the row-template.component.ts.
Then update row-template.component.ts:
import { Component } from '@angular/core';
import { Player } from '../../player';
import { CommonModule } from '@angular/common';
import { MatTableModule } from '@angular/material/table';
@Component({
selector: 'app-row-template',
imports: [CommonModule, MatTableModule],
templateUrl: './row-template.component.html',
styleUrl: './row-template.component.scss'
})
export class RowTemplateComponent {
displayedColumns: string[] = ['photo', 'name', 'team'];
dataSource = Player;
}
Update HTML for Custom Row Templates
Open row-template.component.html and replace with:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Photo Column -->
<ng-container matColumnDef="photo">
<th mat-header-cell *matHeaderCellDef> Photo </th>
<td mat-cell *matCellDef="let player">
<img [src]="'/assets/imgs/' + player.photo" alt="{{player.name}}" width="50" height="50">
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let player">
<strong>{{player.name}}</strong>
</td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let player">
<span style="color: #555;">{{player.team}}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
To prevent 404 errors when loading images, add all images from the GitHub repository. In angular.json add:
"architect": {
"build": {
...
"options": {
...
"assets": [
...
"src/assets",
"src/images"
],
...
},
...
}
...
}
Style the Table
Add the following to row-template.component.scss:
table {
width: 100%;
img {
border-radius: 4px;
}
td {
vertical-align: middle;
}
}
Add Route and Navigation
In app.routes.ts, add:
import { Routes } from '@angular/router';
import { BasicTableComponent } from './components/basic-table/basic-table.component';
import { RemoteDataTableComponent } from './components/remote-data-table/remote-data-table.component';
import { RowTemplateComponent } from './components/row-template/row-template.component';
export const routes: Routes = [
{ path: 'basic-table', component: BasicTableComponent },
{ path: 'remote-data-table', component: RemoteDataTableComponent },
{ path: 'row-template', component: RowTemplateComponent },
];
Update app.component.html to include the new link:
<div class="main">
<h1>Angular Table</h1>
<ul>
<li><a routerLink="/basic-table" routerLinkActive="active">Basic Table</a></li>
<li><a routerLink="/remote-data-table" routerLinkActive="active">REST API Table</a></li>
<li><a routerLink="/row-template" routerLinkActive="active">Row Templates</a></li>
</ul>
<router-outlet />
</div>
Result
When you run the app (ng serve --open) and navigate to /row-template, you’ll see each row with a photo, styled name, and team, rendered using custom HTML templates.
Step 5: Pagination Example
Pagination in Angular Material is handled using the MatPaginator component, which allows you to split long tables into pages.
Add Required Imports
In your app.module.ts (or component if using standalone), import:
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatTableModule } from '@angular/material/table';
If you're using standalone components:
@Component({
standalone: true,
imports: [MatTableModule, MatPaginatorModule],
...
})
Create the Component
Run this command:
ng g c components/pagination-table
Update the component to use pagination:
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { Player } from '../../player';
@Component({
selector: 'app-pagination-table',
imports: [MatTableModule, MatPaginatorModule],
templateUrl: './pagination-table.component.html',
styleUrl: './pagination-table.component.scss'
})
export class PaginationTableComponent implements AfterViewInit {
displayedColumns: string[] = ['name', 'team'];
dataSource = new MatTableDataSource(Player);
@ViewChild(MatPaginator) paginator!: MatPaginator;
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
}
}
In pagination-table.component.html:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let player"> {{player.name}} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let player"> {{player.team}} </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, 25]" showFirstLastButtons></mat-paginator>
In pagination-table.component.scss:
table {
width: 100%;
}
Add Route
In app.routes.ts:
import { Routes } from '@angular/router';
import { BasicTableComponent } from './components/basic-table/basic-table.component';
import { RemoteDataTableComponent } from './components/remote-data-table/remote-data-table.component';
import { RowTemplateComponent } from './components/row-template/row-template.component';
import { PaginationTableComponent } from './components/pagination-table/pagination-table.component';
export const routes: Routes = [
{ path: 'basic-table', component: BasicTableComponent },
{ path: 'remote-data-table', component: RemoteDataTableComponent },
{ path: 'row-template', component: RowTemplateComponent },
{ path: 'pagination-table', component: PaginationTableComponent },
];
Update Menu (Optional)
In app.component.html, add a new link:
<li><a routerLink="/pagination-table" routerLinkActive="active">Pagination Table</a></li>
Run the App
ng serve --open
Go to:
http://localhost:4200/pagination-table
You’ll now see the table with pagination enabled.
Step 6: Sorting Example
Angular Material provides the MatSort directive for enabling column-based sorting in <mat-table>.
Add Required Imports
In your component (or module), add:
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
If you're using standalone components:
@Component({
...
standalone: true,
imports: [MatTableModule, MatSortModule],
})
Generate a New Component
ng g c components/sorting-table
In sorting-table.component.ts:
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { Player } from '../../player';
@Component({
selector: 'app-sorting-table',
standalone: true,
imports: [MatTableModule, MatSortModule],
templateUrl: './sorting-table.component.html',
styleUrl: './sorting-table.component.scss'
})
export class SortingTableComponent implements AfterViewInit {
displayedColumns: string[] = ['name', 'team'];
dataSource = new MatTableDataSource(Player);
@ViewChild(MatSort) sort!: MatSort;
ngAfterViewInit() {
this.dataSource.sort = this.sort;
}
}
In sorting-table.component.html:
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let player"> {{player.name}} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Team </th>
<td mat-cell *matCellDef="let player"> {{player.team}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
In sorting-table.component.scss:
table {
width: 100%;
}
Add Route
In your app.routes.ts:
...
import { SortingTableComponent } from './components/sorting-table/sorting-table.component';
export const routes: Routes = [
...
{ path: 'sorting-table', component: SortingTableComponent },
];
Update Menu
In app.component.html:
<li><a routerLink="/sorting-table" routerLinkActive="active">Sorting Table</a></li>
Run the App
ng serve --open
Navigate to:
http://localhost:4200/sorting-table
Now you’ll have a fully functional sortable table! Click the column headers to sort.
Step 7: Filtering Example
Angular Material’s MatTableDataSource includes built-in filtering functionality. You just need a filter input and a way to bind its value.
Required Imports
If you're using standalone components, ensure this is imported:
import { MatTableModule } from '@angular/material/table';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
Then include them in your component's imports.
Generate a New Component
ng g c components/filtering-table
In filtering-table.component.ts:
import { Component } from '@angular/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatInputModule } from '@angular/material/input';
import { Player } from '../../player';
@Component({
selector: 'app-filtering-table',
imports: [
MatTableModule,
MatFormFieldModule,
MatInputModule
],
templateUrl: './filtering-table.component.html',
styleUrl: './filtering-table.component.scss'
})
export class FilteringTableComponent {
displayedColumns: string[] = ['name', 'team'];
dataSource = new MatTableDataSource(Player);
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
}
In filtering-table.component.html:
<mat-form-field appearance="outline" class="filter-form">
<mat-label>Filter players</mat-label>
<input matInput (keyup)="applyFilter($event)" placeholder="Type player or team name">
</mat-form-field>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let player"> {{player.name}} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let player"> {{player.team}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
In filtering-table.component.scss:
.filter-form {
margin-bottom: 16px;
width: 100%;
max-width: 400px;
}
Add Route
In your app.routes.ts:
import { FilteringTableComponent } from './components/filtering-table/filtering-table.component';
...
{ path: 'filtering-table', component: FilteringTableComponent },
...
Update Menu
In app.component.html, add:
...
<li><a routerLink="/filtering-table" routerLinkActive="active">Filtering Table</a></li>
...
Run It
ng serve --open
Visit http://localhost:4200/filtering-table
You should now see a fully working filterable Angular Material Table.
Step 8: Selection Example
In this step, you'll learn how to enable row selection in a Material Table using checkboxes with SelectionModel.
Required Imports
Make sure you import the necessary modules:
import { MatTableModule } from '@angular/material/table';
import { MatCheckboxModule } from '@angular/material/checkbox';
Include them in your standalone component's imports array.
Generate the Component
ng g c components/selection-table
In your selection-table.component.ts:
import { Component } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { SelectionModel } from '@angular/cdk/collections';
import { Player } from '../../player';
@Component({
selector: 'app-selection-table',
imports: [MatTableModule, MatCheckboxModule],
templateUrl: './selection-table.component.html',
styleUrl: './selection-table.component.scss'
})
export class SelectionTableComponent {
displayedColumns: string[] = ['select', 'name', 'team'];
dataSource = new MatTableDataSource(Player);
selection = new SelectionModel<any>(true, []);
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected()
? this.selection.clear()
: this.dataSource.data.forEach(row => this.selection.select(row));
}
}
In your selection-table.component.html:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Checkbox Column -->
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="masterToggle()" [checked]="isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let player"> {{player.name}} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let player"> {{player.team}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
In your selection-table.component.scss:
table {
width: 100%;
}
Add the Route
In app.routes.ts:
import { SelectionTableComponent } from './components/selection-table/selection-table.component';
{ path: 'selection-table', component: SelectionTableComponent },
Update Navigation Menu
<li><a routerLink="/selection-table" routerLinkActive="active">Selection Table</a></li>
Run the App
ng serve --open
Go to:
http://localhost:4200/selection-table
Now you have row selection with checkboxes working!
Step 9: Footer Row Example
We’ll reuse the Player dataset and show the number of players per team in a footer row.
Create the Component
In your footer-table.component.ts:
import { Component } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
import { Player } from '../../player';
@Component({
selector: 'app-footer-table',
imports: [MatTableModule],
templateUrl: './footer-table.component.html',
styleUrl: './footer-table.component.scss'
})
export class FooterTableComponent {
displayedColumns: string[] = ['name', 'team'];
dataSource = Player;
get totalPlayers(): number {
return this.dataSource.length;
}
get uniqueTeams(): number {
const teams = this.dataSource.map(p => p.team);
return new Set(teams).size;
}
}
In your footer-table.component.html:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let player"> {{player.name}} </td>
<td mat-footer-cell *matFooterCellDef> Total Players: {{ totalPlayers }} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let player"> {{player.team}} </td>
<td mat-footer-cell *matFooterCellDef> Teams: {{ uniqueTeams }} </td>
</ng-container>
<!-- Header, Rows, Footer -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr mat-footer-row *matFooterRowDef="displayedColumns"></tr>
</table>
In your footer-table.component.scss:
table {
width: 100%;
}
td,
th {
padding: 8px;
}
mat-footer-cell {
font-weight: bold;
background-color: #f5f5f5;
}
Add Route to footer-table
In app.routes.ts:
import { FooterTableComponent } from './components/footer-table/footer-table.component';
{ path: 'footer-table', component: FooterTableComponent },
Add Link to Navigation
In app.component.html:
<li><a routerLink="/footer-table" routerLinkActive="active">Footer Table</a></li>
Run the App
ng serve --open
Visit:
http://localhost:4200/footer-table
You should now see a footer row that shows the total number of players and distinct teams.
Step 10: Sticky Rows & Columns Example
Sticky rows and columns help improve data visibility when scrolling large tables.
Generate the Component
In your sticky-table.component.ts:
import { Component } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
import { Player } from '../../player';
@Component({
selector: 'app-sticky-table',
imports: [MatTableModule],
templateUrl: './sticky-table.component.html',
styleUrl: './sticky-table.component.scss'
})
export class StickyTableComponent {
displayedColumns: string[] = ['id', 'name', 'team'];
dataSource = Player;
}
In your sticky-table.component.html:
<div class="table-container">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- ID Column (Sticky) -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef sticky> ID </th>
<td mat-cell *matCellDef="let element" sticky> {{element.id}} </td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef> Team </th>
<td mat-cell *matCellDef="let element"> {{element.team}} </td>
</ng-container>
<!-- Sticky Header Row -->
<tr mat-header-row *matHeaderRowDef="displayedColumns" sticky></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
In your sticky-table.component.scss:
.table-container {
max-height: 400px;
overflow: auto;
}
table {
width: 100%;
min-width: 600px;
}
th.mat-header-cell,
td.mat-cell {
min-width: 120px;
}
Add the Route
In app.routes.ts:
import { StickyTableComponent } from './components/sticky-table/sticky-table.component';
{ path: 'sticky-table', component: StickyTableComponent },
Add Link to Navigation
In app.component.html:
<li><a routerLink="/sticky-table" routerLinkActive="active">Sticky Table</a></li>
Run the App
ng serve --open
Navigate to:
http://localhost:4200/sticky-table
Now you’ll see the header row and first column (ID) stay in place while you scroll down or sideways.
Step 11: Styling Table Example
In this step, you’ll add custom styles and Angular Material theming to make your table cleaner, more modern, and easier to read.
Generate the Component
ng g c components/styled-table
In your styled-table.component.ts:
import { Component } from '@angular/core';
import { MatTableModule } from '@angular/material/table';
import { Player } from '../../player';
@Component({
selector: 'app-styled-table',
imports: [MatTableModule],
templateUrl: './styled-table.component.html',
styleUrl: './styled-table.component.scss'
})
export class StyledTableComponent {
displayedColumns: string[] = ['photo', 'name', 'team'];
dataSource = Player;
}
In your styled-table.component.html:
<table mat-table [dataSource]="dataSource" class="styled-table mat-elevation-z8">
<!-- Photo Column -->
<ng-container matColumnDef="photo">
<th mat-header-cell *matHeaderCellDef class="styled-th"> Photo </th>
<td mat-cell *matCellDef="let player">
<img [src]="'/assets/imgs/' + player.photo" alt="{{player.name}}" class="player-photo" />
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef class="styled-th"> Name </th>
<td mat-cell *matCellDef="let player"> {{player.name}} </td>
</ng-container>
<!-- Team Column -->
<ng-container matColumnDef="team">
<th mat-header-cell *matHeaderCellDef class="styled-th"> Team </th>
<td mat-cell *matCellDef="let player"> {{player.team}} </td>
</ng-container>
<!-- Rows -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
In your styled-table.component.scss:
.styled-table {
width: 100%;
border-radius: 8px;
overflow: hidden;
th.mat-header-cell {
background-color: #3f51b5;
color: white;
font-weight: bold;
text-transform: uppercase;
font-size: 14px;
}
td.mat-cell {
font-size: 14px;
padding: 10px 16px;
vertical-align: middle;
}
tr.mat-row:nth-child(even) {
background-color: #f9f9f9;
}
.player-photo {
height: 40px;
width: 40px;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.styled-th {
background-color: #3f51b5;
color: white;
}
}
Add Route
In app.routes.ts:
import { StyledTableComponent } from './components/styled-table/styled-table.component';
{ path: 'styled-table', component: StyledTableComponent },
Add Link to Navigation
In app.component.html:
<li><a routerLink="/styled-table" routerLinkActive="active">Styled Table</a></li>
Run the App
ng serve --open
Go to
http://localhost:4200/styled-table
You should now see a beautifully styled table with alternating row colors, bold headers, and circular player photos.
Conclusion
In this comprehensive Angular Material Table tutorial, you've learned how to create powerful, responsive, and fully-featured tables using the <mat-table> component. From basic table setup to integrating a REST API, applying custom templates, enabling pagination, sorting, filtering, row selection, sticky headers, and polished styling — you've seen how Angular Material can handle even the most demanding data presentation needs.
Using these step-by-step examples, you now have the foundation to build production-ready data tables in your Angular 19+ apps with ease.
If you found this tutorial helpful, feel free to share it with other Angular developers and consider subscribing to Djamware for more up-to-date tutorials on Angular, React, Node.js, and full-stack development.
You can find the full source code of this tutorial on our GitHub.
If you don’t want to waste your time design your own front-end or your budget to spend by hiring a web designer then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.
That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap course:
- Master en JavaScript: Aprender JS, jQuery, Angular 8, NodeJS
- Angular 8 - Complete Essential Guide
- Learn Angular 8 by creating a simple Full Stack Web App
- Angular 5 Bootcamp FastTrack
- Angular 6 - Soft & Sweet
- Angular 6 with TypeScript
Thanks!