@bianca-ioana-colta/mat-multi-sort
v1.0.2
Published
Multi-column sorting for Angular Material tables (mat-table). Drop-in replacement for matSort and mat-sort-header. Supports Angular and Angular Material 16 to 22.
Downloads
535
Maintainers
Readme
@bianca-ioana-colta/mat-multi-sort
Multi-column sorting for Angular Material tables (mat-table).
Click several column headers and the table is sorted by all of them, in the order you clicked them. Each sorted column shows its arrow and its priority number (1, 2, 3...).
- Works with Angular / Angular Material 16, 17, 18, 19, 20, 21 and 22.
- Drop-in replacement for
matSort/mat-sort-header. - Works with standalone components and with NgModules.
- Client-side sorting (
MatTableDataSource) or server-side sorting (you receive the full sort list).
Contents
- Requirements
- Install the package
- Update the component (.ts)
- Update the template (.html)
- Run and check
- Optional features
- Troubleshooting
- API reference
1. Requirements
Your project must already have Angular Material installed, in the same major version as @angular/core.
Check:
npm ls @angular/core @angular/material @angular/cdkIf @angular/material is missing, install it first:
ng add @angular/materialYour table should already work with a normal mat-table. The steps below assume you start from a table that
uses (or would use) the standard Angular Material sort: matSort + mat-sort-header.
2. Install the package
Run this in the folder that contains your app's package.json:
npm install @bianca-ioana-colta/mat-multi-sortCheck that package.json now contains:
"dependencies": {
"@bianca-ioana-colta/mat-multi-sort": "^1.0.2"
}If your company
.npmrcpoints to a private registry and the install fails withE401/E404, add--registry https://registry.npmjs.org/to the command.
3. Update the component (.ts)
Open the component that contains the table, for example users-table.component.ts.
3.1 Imports at the top of the file
Remove (if you have it):
import { MatSort, MatSortModule } from '@angular/material/sort';Add:
import {
MatMultiSort,
MatMultiSortHeader,
MatMultiSortState,
connectMultiSort,
} from '@bianca-ioana-colta/mat-multi-sort';3.2 The imports array of the component
Using NgModules instead of standalone components? Skip this and go to 3.2 (NgModule).
Remove MatSortModule and add MatMultiSort and MatMultiSortHeader:
@Component({
selector: 'app-users-table',
standalone: true,
imports: [
MatTableModule,
// MatSortModule, <- remove
MatMultiSort, // <- add
MatMultiSortHeader, // <- add
],
templateUrl: './users-table.component.html',
})3.2 (NgModule apps)
In the module that declares your table component (for example shared.module.ts):
import { MatMultiSortModule } from '@bianca-ioana-colta/mat-multi-sort';
@NgModule({
declarations: [UsersTableComponent],
imports: [
MatTableModule,
// MatSortModule, <- remove (unless other tables still use matSort)
MatMultiSortModule, // <- add
],
})
export class SharedModule {}3.3 The @ViewChild
Replace:
@ViewChild(MatSort) sort!: MatSort;with:
@ViewChild(MatMultiSort, { static: true }) sort!: MatMultiSort;3.4 Connect the sort to the data source
In ngAfterViewInit (or wherever you set dataSource.sort):
Replace:
this.dataSource.sort = this.sort;with:
connectMultiSort(this.dataSource, this.sort);Keep everything else as it is: paginator, filter, and your custom sortingDataAccessor (if you have one)
keep working. connectMultiSort uses your sortingDataAccessor for every sorted column.
Sorting on the server instead? Do not call
connectMultiSort. See 6.3 Server-side sorting.
Full example (.ts)
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import {
MatMultiSort,
MatMultiSortHeader,
MatMultiSortState,
connectMultiSort,
} from '@bianca-ioana-colta/mat-multi-sort';
export interface User {
name: string;
age: number;
city: string;
}
@Component({
selector: 'app-users-table',
standalone: true,
imports: [MatTableModule, MatMultiSort, MatMultiSortHeader],
templateUrl: './users-table.component.html',
})
export class UsersTableComponent implements AfterViewInit {
@ViewChild(MatMultiSort, { static: true }) sort!: MatMultiSort;
readonly displayedColumns = ['name', 'age', 'city'];
readonly dataSource = new MatTableDataSource<User>([
{ name: 'Ana', age: 30, city: 'Cluj' },
{ name: 'Bogdan', age: 25, city: 'Iasi' },
{ name: 'Carmen', age: 30, city: 'Brasov' },
]);
ngAfterViewInit(): void {
connectMultiSort(this.dataSource, this.sort);
}
}4. Update the template (.html)
Open the template of the same component, for example users-table.component.html.
4.1 The <table> element
Replace matSort with matMultiSort:
<!-- before -->
<table mat-table [dataSource]="dataSource" matSort>
<!-- after -->
<table mat-table [dataSource]="dataSource" matMultiSort></table>
</table>If you had matSortActive / matSortDirection for an initial sort, remove them and use
6.1 Initial sort instead.
4.2 Every sortable header
Replace mat-sort-header with mat-multi-sort-header on every <th>:
<!-- before -->
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<!-- after -->
<th mat-header-cell *matHeaderCellDef mat-multi-sort-header>Name</th>The same goes for the bound form:
<!-- before -->
<th mat-header-cell *matHeaderCellDef [mat-sort-header]="'name'">Name</th>
<!-- after -->
<th mat-header-cell *matHeaderCellDef [mat-multi-sort-header]="'name'">Name</th>Important: search the file for
mat-sort-headerand make sure none is left. If one remains after removingMatSortModule, Angular fails withNG8002: Can't bind to 'mat-sort-header' since it isn't a known property of 'th'.
The sort id is the matColumnDef name by default, so mat-multi-sort-header without a value is enough when
the column name matches the property of the row (row.name). Give an explicit id only when it differs:
mat-multi-sort-header="fullName".
start="desc", disabled and disableClear work the same as on mat-sort-header.
Full example (.html)
<table mat-table [dataSource]="dataSource" matMultiSort>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-multi-sort-header>Name</th>
<td mat-cell *matCellDef="let row">{{ row.name }}</td>
</ng-container>
<ng-container matColumnDef="age">
<th mat-header-cell *matHeaderCellDef mat-multi-sort-header start="desc">Age</th>
<td mat-cell *matCellDef="let row">{{ row.age }}</td>
</ng-container>
<ng-container matColumnDef="city">
<th mat-header-cell *matHeaderCellDef mat-multi-sort-header>City</th>
<td mat-cell *matCellDef="let row">{{ row.city }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>5. Run and check
npm startOpen the page with the table and check:
| Action | Expected result |
| ------------------------------------------- | ------------------------------------------------------------------------- |
| Hover a header | A faint arrow appears. |
| Click a header once | Sorted by that column (start direction, asc by default). Arrow visible. |
| Click a second header | Sorted by both. Numbers 1 and 2 appear next to the arrows. |
| Click a sorted header again | Its direction is reversed. The priority stays the same. |
| Click it a third time | The column is removed from the sort; the others move up. |
| Move the mouse away from an unsorted header | No arrow is shown. |
6. Optional features
6.1 Initial sort
Component:
protected readonly INITIAL_SORT: MatMultiSortState[] = [
{ active: 'age', direction: 'desc' },
{ active: 'name', direction: 'asc' },
];Template:
<table mat-table [dataSource]="dataSource" matMultiSort [matMultiSortState]="INITIAL_SORT"></table>6.2 "Clear sorting" button
<button type="button" (click)="sort.clear()">Clear sorting</button>6.3 Server-side sorting
Do not call connectMultiSort. Listen to matMultiSortChange and send the list to your API:
<table mat-table [dataSource]="rows" matMultiSort (matMultiSortChange)="onSort($event)"></table>onSort(sorts: MatMultiSortState[]): void {
// e.g. [{ active: 'age', direction: 'desc' }, { active: 'name', direction: 'asc' }]
// first item = highest priority; empty array = no sorting
this.loadData(sorts);
}6.4 Read the current sort
const current = this.sort.sorts(); // readonly MatMultiSortState[]6.5 Arrow position and colours
<th mat-header-cell *matHeaderCellDef mat-multi-sort-header arrowPosition="before">Name</th>.mat-multi-sort-header {
--mat-multi-sort-arrow-color: #1976d2;
--mat-multi-sort-order-color: #1976d2;
}7. Troubleshooting
| Problem | Fix |
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| NG8002: Can't bind to 'mat-sort-header' | A mat-sort-header is still in the template. Replace it with mat-multi-sort-header. |
| NG8002: Can't bind to 'mat-multi-sort-header' / matMultiSortState | MatMultiSort and MatMultiSortHeader (or MatMultiSortModule) are missing from imports. |
| Clicking headers does nothing | <table> still has matSort instead of matMultiSort, or connectMultiSort is not called. |
| Only one column is sorted at a time | You still assign this.dataSource.sort = this.sort instead of connectMultiSort(this.dataSource, this.sort). |
| Dates sort as text | Set dataSource.sortingDataAccessor and return row.date.getTime() for the date column. |
| npm install fails with E401 / E404 | Your .npmrc uses a private registry. Add --registry https://registry.npmjs.org/. |
| ERESOLVE peer dependency error | @angular/material / @angular/cdk major version is different from @angular/core, or is older than 16. |
8. API reference
MatMultiSort - directive [matMultiSort]
Extends MatSort, so MatTableDataSource, matSortChange, start, disableClear and matSortDisabled keep
working (they describe the first / primary sort).
| Member | Description |
| --------------------------------------------- | ------------------------------------------------------- |
| @Input() matMultiSortState | Sets the sort list (no change event is emitted). |
| @Output() matMultiSortChange | Emits MatMultiSortState[] after a click or clear(). |
| sorts: Signal<readonly MatMultiSortState[]> | Current sorts, in priority order. |
| clear() | Removes all sorts and emits matMultiSortChange. |
MatMultiSortHeader - component [mat-multi-sort-header]
| Input | Description |
| ----------------------- | --------------------------------------------------------- |
| mat-multi-sort-header | Sort id. Defaults to the matColumnDef name. |
| start | 'asc' or 'desc': first direction for this column. |
| disableClear | The third click goes back to start instead of removing. |
| disabled | Column cannot be sorted. |
| arrowPosition | 'before' or 'after' (default). |
MatMultiSortState
interface MatMultiSortState {
active: string; // column id
direction: 'asc' | 'desc';
}Helpers
connectMultiSort(dataSource, sort): client-side multi-column sorting forMatTableDataSource.sortByMultiSort(data, sorts, accessor): returns a sorted copy of an array (for custom data sources).
MatMultiSortModule
NgModule that exports MatMultiSort and MatMultiSortHeader, for apps that use NgModules.
License
MIT (c) 2026 Bianca Colta. See LICENSE.
