Overview
The Angular Material Table component is used to display data in a tabular format. It provides a flexible and customizable way to display data, including features like sorting, pagination, and filtering.
Angular Material team provides sorting and pagination using MatSort
and MatPaginator
respectively. For server side filtering, we need to implement a custom logic. Let's see how to do that.
Creating a table with server side data
Let's create a table with server side data.
1. Sample database
We will use GutHub API for this example. Create a file src\app\table\database.ts
with below content:
import { HttpClient } from '@angular/common/http';
import { SortDirection } from '@angular/material/sort';
import { Observable } from 'rxjs';
export interface GithubApi {
items: GithubIssue[];
total_count: number;
}
export interface GithubIssue {
created_at: string;
number: string;
state: string;
title: string;
}
export class ExampleHttpDatabase {
constructor(private _httpClient: HttpClient) {}
getRepoIssues(
sort: string,
order: SortDirection,
page: number,
pageSize = 10,
query = ''
): Observable<GithubApi> {
const href = 'https://api.github.com/search/issues';
const requestUrl = `${href}?q=${encodeURIComponent(
query + ' ' + 'repo:angular/components'
)}&sort=${sort}&order=${order}&page=${page + 1}&per_page=${pageSize}`;
return this._httpClient.get<GithubApi>(requestUrl);
}
}
2. Table Data-source
Create a file src\app\table\data-source.ts
with below content:
import { DataSource } from '@angular/cdk/collections';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { Observable, of as observableOf, merge } from 'rxjs';
import { GithubIssue, ExampleHttpDatabase } from './database';
import { signal } from '@angular/core';
// TODO: Replace this with your own data model type
export interface TableItem extends GithubIssue {}
/**
* Data source for the Table view. This class should
* encapsulate all logic for fetching and manipulating the displayed data
* (including sorting, pagination, and filtering).
*/
export class TableDataSource extends DataSource<TableItem> {
data: TableItem[] = [];
paginator: MatPaginator | undefined;
sort: MatSort | undefined;
database: ExampleHttpDatabase | undefined;
resultsLength = signal(0);
isLoadingResults = signal(true);
isRateLimitReached = signal(false);
constructor() {
super();
}
/**
* Connect this data source to the table. The table will only update when
* the returned stream emits new items.
* @returns A stream of the items to be rendered.
*/
connect(): Observable<TableItem[]> {
if (this.paginator && this.sort && this.database) {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
return merge(this.paginator.page, this.sort.sortChange).pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults.set(true);
return this.database!.getRepoIssues(
this.sort!.active,
this.sort!.direction,
this.paginator!.pageIndex,
this.paginator!.pageSize
).pipe(
catchError(() => observableOf({ items: [], total_count: 0 })),
map((data) => {
// Flip flag to show that loading has finished.
this.isLoadingResults.set(false);
this.isRateLimitReached.set(data === null);
this.resultsLength.set(data.total_count);
return data.items;
})
);
})
);
} else {
throw Error(
'Please set the paginator, sort and database on the data source before connecting.'
);
}
}
/**
* Called when the table is being destroyed. Use this function, to clean up
* any open connections or free any held resources that were set up during connect.
*/
disconnect(): void {}
}
3. Table component
Create a file src\app\table\table.component.ts
with below content:
import {
AfterViewInit,
Component,
inject,
signal,
ViewChild,
computed,
} from '@angular/core';
import { MatTableModule, MatTable } from '@angular/material/table';
import { MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
import { MatSortModule, MatSort } from '@angular/material/sort';
import { TableDataSource, TableItem } from './table-datasource';
import { ExampleHttpDatabase } from './database';
import { HttpClient } from '@angular/common/http';
import { DatePipe } from '@angular/common';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrl: './table.component.scss',
standalone: true,
imports: [
MatTableModule,
MatPaginatorModule,
MatSortModule,
DatePipe,
MatProgressSpinnerModule,
],
})
export class TableComponent implements AfterViewInit {
private _httpClient = inject(HttpClient);
private database = new ExampleHttpDatabase(this._httpClient);
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatTable) table!: MatTable<TableItem>;
dataSource: TableDataSource;
resultsLength = signal(0);
isLoadingResults = signal(false);
isRateLimitReached = signal(false);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns: string[] = ['created', 'state', 'number', 'title'];
constructor() {
this.dataSource = new TableDataSource();
}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
this.dataSource.database = this.database;
this.table.dataSource = this.dataSource;
this.resultsLength = this.dataSource.resultsLength;
this.isLoadingResults = this.dataSource.isLoadingResults;
this.isRateLimitReached = this.dataSource.isRateLimitReached;
}
}
4. Table component template
Create a file src\app\table\table.component.html
with below content:
<div class="example-container mat-elevation-z8">
@if (isLoadingResults() || isRateLimitReached()) {
<div class="example-loading-shade">
@if (isLoadingResults()) {
<mat-spinner></mat-spinner>
}
@if (isRateLimitReached()) {
<div class="example-rate-limit-reached">
GitHub's API rate limit has been reached. It will be reset in one minute.
</div>
}
</div>
}
<div class="example-table-container">
<table mat-table class="example-table"
matSort matSortActive="created" matSortDisableClear matSortDirection="desc">
<!-- Number Column -->
<ng-container matColumnDef="number">
<th mat-header-cell *matHeaderCellDef>#</th>
<td mat-cell *matCellDef="let row">{{row.number}}</td>
</ng-container>
<!-- Title Column -->
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef>Title</th>
<td mat-cell *matCellDef="let row">{{row.title}}</td>
</ng-container>
<!-- State Column -->
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef>State</th>
<td mat-cell *matCellDef="let row">{{row.state}}</td>
</ng-container>
<!-- Created Column -->
<ng-container matColumnDef="created">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>
Created
</th>
<td mat-cell *matCellDef="let row">{{row.created_at | date}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<mat-paginator [length]="resultsLength()" [pageSize]="30" aria-label="Select page of GitHub search results"></mat-paginator>
</div>
5. Table component style
Create a file src\app\table\table.component.scss
with below content:
.example-container {
position: relative;
}
.example-table-container {
position: relative;
min-height: 200px;
max-height: 400px;
overflow: auto;
}
table {
width: 100%;
}
.example-loading-shade {
position: absolute;
top: 0;
left: 0;
bottom: 56px;
right: 0;
background: rgba(0, 0, 0, 0.15);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
.example-rate-limit-reached {
max-width: 360px;
text-align: center;
}
/* Column Widths */
.mat-column-number,
.mat-column-state {
width: 64px;
}
.mat-column-created {
width: 124px;
}
If you look at the output, you will see sorting and pagination working as expected.
Server side filtering
To implement server side filtering, we will use a similar approach as sorting and pagination.
1. MatTextFiler
directive
We will create a MatTextFiler
directive which we can add it to the text field to filter the table.
Create a file src\app\table\mat-text-filter.directive.ts
with below content:
import { EventEmitter, HostListener } from '@angular/core';
import { Directive } from '@angular/core';
export class MatTextFilter {
private _term = '';
public get term() {
return this._term;
}
public set term(value) {
this._term = value;
this.textFilterChange.emit(value);
}
textFilterChange = new EventEmitter<string>();
}
@Directive({
selector: 'input[matTextFilter]',
exportAs: 'matTextFilter',
standalone: true,
})
export class MatTextFilterDirective {
matTextFilter = new MatTextFilter();
@HostListener('change', ['$event.target.value'])
onChange(value: string) {
this.matTextFilter.term = value;
}
}
Let's understand the code above.
- We have created a
MatTextFilter
class which has aterm
property and atextFilterChange
event emitter. - We have created a
MatTextFilterDirective
directive which we can add it to the text field to filter the table. - We have used
@HostListener
to listen to the change event of the text field and emit thetextFilterChange
event.
2. Using MatTextFilterDirective
in template
We will add a text field to the table to filter the table. And attach MatTextFilterDirective
to the text field.
<div class="example-table-container">
<mat-form-field><!-- [!code ++] -->
<input matTextFilter matInput placeholder="Filter" #textFilter="matTextFilter"><!-- [!code ++] -->
</mat-form-field><!-- [!code ++] -->
</div>
3. Using MatTextFilterDirective
in component
Not let's see how to use MatTextFilterDirective
in our table component.
@Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrl: './table.component.scss',
standalone: true,
imports: [
// other imports
MatFormFieldModule,// [!code ++]
MatInputModule,// [!code ++]
MatTextFilterDirective,// [!code ++]
],
})
export class TableComponent implements AfterViewInit {
@ViewChild(MatTextFilterDirective) textFilter!: MatTextFilterDirective;// [!code ++]
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
this.dataSource.textFilter = this.textFilter.matTextFilter;// [!code ++]
this.dataSource.database = this.database;
// other code
}
}
4. Using MatTextFilterDirective
in datasource
We will use MatTextFilter
in our datasource to filter the data, so that we can listen to the text filter change event and make a call to the server to filter the data.
export class TableDataSource extends DataSource<TableItem> {
textFilter: MatTextFilter | undefined;
connect(): Observable<TableItem[]> {
if (this.paginator && this.sort && this.database && this.textFilter) {// [!code highlight]
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
return merge(
this.paginator.page,
this.sort.sortChange,
this.textFilter.textFilterChange// [!code highlight]
).pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults.set(true);
return this.database!.getRepoIssues(
this.sort!.active,
this.sort!.direction,
this.paginator!.pageIndex,
this.paginator!.pageSize,
this.textFilter!.term// [!code highlight]
).pipe(
catchError(() => observableOf({ items: [], total_count: 0 })),
map((data) => {
// Flip flag to show that loading has finished.
this.isLoadingResults.set(false);
this.isRateLimitReached.set(data === null);
this.resultsLength.set(data.total_count);
return data.items;
})
);
})
);
} else {
throw Error(
'Please set the paginator, sort and database on the data source before connecting.'
);
}
}
}
That's it. We have implemented server side filtering in Angular Material Table.
Top comments (2)
Cool
Hi Dharmen Shah,
Thanks for sharing.