diff --git a/src/components-examples/material/table/table-sorting/table-sorting-example.css b/src/components-examples/material/table/table-sorting/table-sorting-example.css
index 11b40820cb5b..1ef953462a3b 100644
--- a/src/components-examples/material/table/table-sorting/table-sorting-example.css
+++ b/src/components-examples/material/table/table-sorting/table-sorting-example.css
@@ -5,3 +5,7 @@ table {
th.mat-sort-header-sorted {
color: black;
}
+
+.example-sorting-toggle-group {
+ margin: 8px;
+}
diff --git a/src/components-examples/material/table/table-sorting/table-sorting-example.html b/src/components-examples/material/table/table-sorting/table-sorting-example.html
index 76edcae012cb..c6ba18115b7e 100644
--- a/src/components-examples/material/table/table-sorting/table-sorting-example.html
+++ b/src/components-examples/material/table/table-sorting/table-sorting-example.html
@@ -1,36 +1,52 @@
+
+
+ Single column sorting
+ Multi column sorting
+
+
+
-
+
- No.
+ First name
|
- {{element.position}} |
+ {{element.firstName}} |
-
+
- Name
+ Last name
|
- {{element.name}} |
+ {{element.lastName}} |
-
+
- Weight
+ Position
+ |
+ {{element.position}} |
+
+
+
+
+
+ Office
|
- {{element.weight}} |
+ {{element.office}} |
-
+
- Symbol
+ Salary
|
- {{element.symbol}} |
+ {{element.salary}} |
diff --git a/src/components-examples/material/table/table-sorting/table-sorting-example.ts b/src/components-examples/material/table/table-sorting/table-sorting-example.ts
index 6435b284d456..9689e43a9d8b 100644
--- a/src/components-examples/material/table/table-sorting/table-sorting-example.ts
+++ b/src/components-examples/material/table/table-sorting/table-sorting-example.ts
@@ -2,25 +2,34 @@ import {LiveAnnouncer} from '@angular/cdk/a11y';
import {AfterViewInit, Component, ViewChild} from '@angular/core';
import {MatSort, Sort, MatSortModule} from '@angular/material/sort';
import {MatTableDataSource, MatTableModule} from '@angular/material/table';
+import {MatButtonToggle, MatButtonToggleGroup} from '@angular/material/button-toggle';
+import {MatButton} from '@angular/material/button';
-export interface PeriodicElement {
- name: string;
- position: number;
- weight: number;
- symbol: string;
+export interface EmployeeData {
+ firstName: string;
+ lastName: string;
+ position: string;
+ office: string;
+ salary: number;
}
-const ELEMENT_DATA: PeriodicElement[] = [
- {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
- {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
- {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
- {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
- {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
- {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
- {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
- {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
- {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
- {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
-];
+
+const MULTI_SORT_DATA: EmployeeData[] = [
+ {firstName: "Garrett", lastName: "Winters", position: "Accountant", office: "Tokyo", salary: 170750},
+ {firstName: "Airi", lastName: "Satou", position: "Accountant", office: "Tokyo", salary: 162700},
+ {firstName: "Donna", lastName: "Snider", position: "Customer Support", office: "New York", salary: 112000},
+ {firstName: "Serge", lastName: "Baldwin", position: "Data Coordinator", office: "Singapore", salary: 138575},
+ {firstName: "Thor", lastName: "Walton", position: "Developer", office: "New York", salary: 98540},
+ {firstName: "Gavin", lastName: "Joyce", position: "Developer", office: "Edinburgh", salary: 92575},
+ {firstName: "Suki", lastName: "Burks", position: "Developer", office: "London", salary: 114500},
+ {firstName: "Jonas", lastName: "Alexander", position: "Developer", office: "San Francisco", salary: 86500},
+ {firstName: "Jackson", lastName: "Bradshaw", position: "Director", office: "New York", salary: 645750},
+ {firstName: "Brielle", lastName: "Williamson", position: "Integration Specialist", office: "New York", salary: 372000},
+ {firstName: "Michelle", lastName: "House", position: "Integration Specialist", office: "Sydney", salary: 95400},
+ {firstName: "Michael", lastName: "Bruce", position: "Javascript Developer", office: "Singapore", salary: 183000},
+ {firstName: "Ashton", lastName: "Cox", position: "Junior Technical Author", office: "San Francisco", salary: 86000},
+ {firstName: "Michael", lastName: "Silva", position: "Marketing Designer", office: "London", salary: 198500},
+ {firstName: "Timothy", lastName: "Mooney", position: "Office Manager", office: "London", salary: 136200},
+]
/**
* @title Table with sorting
*/
@@ -29,11 +38,11 @@ const ELEMENT_DATA: PeriodicElement[] = [
styleUrls: ['table-sorting-example.css'],
templateUrl: 'table-sorting-example.html',
standalone: true,
- imports: [MatTableModule, MatSortModule],
+ imports: [MatTableModule, MatSortModule, MatButtonToggle, MatButtonToggleGroup, MatButton],
})
export class TableSortingExample implements AfterViewInit {
- displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
- dataSource = new MatTableDataSource(ELEMENT_DATA);
+ displayedColumns: string[] = ['firstName', 'lastName', 'position', 'office', 'salary'];
+ dataSource = new MatTableDataSource(MULTI_SORT_DATA);
constructor(private _liveAnnouncer: LiveAnnouncer) {}
diff --git a/src/material/sort/sort-header.ts b/src/material/sort/sort-header.ts
index db521315de9e..8254fc754965 100644
--- a/src/material/sort/sort-header.ts
+++ b/src/material/sort/sort-header.ts
@@ -294,9 +294,10 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI
/** Whether this MatSortHeader is currently sorted in either ascending or descending order. */
_isSorted() {
+ const currentSortDirection = this._sort.getCurrentSortDirection(this.id);
return (
- this._sort.active == this.id &&
- (this._sort.direction === 'asc' || this._sort.direction === 'desc')
+ this._sort.isActive(this.id) &&
+ (currentSortDirection === 'asc' || currentSortDirection === 'desc')
);
}
@@ -322,7 +323,9 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI
* only be changed once the arrow displays again (hint or activation).
*/
_updateArrowDirection() {
- this._arrowDirection = this._isSorted() ? this._sort.direction : this.start || this._sort.start;
+ this._arrowDirection = this._isSorted()
+ ? this._sort.getCurrentSortDirection(this.id)
+ : this.start || this._sort.start;
}
_isDisabled() {
@@ -340,7 +343,7 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI
return 'none';
}
- return this._sort.direction == 'asc' ? 'ascending' : 'descending';
+ return this._sort.getCurrentSortDirection(this.id) == 'asc' ? 'ascending' : 'descending';
}
/** Whether the arrow inside the sort header should be rendered. */
diff --git a/src/material/sort/sort.spec.ts b/src/material/sort/sort.spec.ts
index 15397a301b65..7c93169e964d 100644
--- a/src/material/sort/sort.spec.ts
+++ b/src/material/sort/sort.spec.ts
@@ -57,6 +57,9 @@ describe('MatSort', () => {
fixture = TestBed.createComponent(SimpleMatSortApp);
component = fixture.componentInstance;
fixture.detectChanges();
+
+ component.matSort.matSortMultiple = false;
+ component.matSort.sortState.clear();
});
it('should have the sort headers register and deregister themselves', () => {
@@ -445,6 +448,24 @@ describe('MatSort', () => {
expect(descriptionElement?.textContent).toBe('Sort 2nd column');
});
+ it('should be able to store sorting for multiple columns when using multiSort', () => {
+ component.matSort.matSortMultiple = true;
+
+ component.start = 'asc';
+ testSingleColumnSortDirectionSequence(fixture, ['asc', 'desc', ''], 'defaultA');
+ testSingleColumnSortDirectionSequence(fixture, ['asc', 'desc', ''], 'defaultB');
+
+ expect(component.matSort.sortState.size).toBe(2);
+
+ const defaultAState = component.matSort.sortState.get('defaultA');
+ expect(defaultAState).toBeTruthy();
+ expect(defaultAState?.direction).toBe(component.start);
+
+ const defaultBState = component.matSort.sortState.get('defaultB');
+ expect(defaultBState).toBeTruthy();
+ expect(defaultBState?.direction).toBe(component.start);
+ });
+
it('should render arrows after sort header by default', () => {
const matSortWithArrowPositionFixture = TestBed.createComponent(MatSortWithArrowPosition);
diff --git a/src/material/sort/sort.ts b/src/material/sort/sort.ts
index 75b97f7067f3..a61c7f001ea7 100644
--- a/src/material/sort/sort.ts
+++ b/src/material/sort/sort.ts
@@ -7,6 +7,7 @@
*/
import {
+ booleanAttribute,
Directive,
EventEmitter,
Inject,
@@ -17,7 +18,7 @@ import {
OnInit,
Optional,
Output,
- booleanAttribute,
+ SimpleChanges,
} from '@angular/core';
import {HasInitialized, mixinInitialized} from '@angular/material/core';
import {Subject} from 'rxjs';
@@ -27,6 +28,7 @@ import {
getSortHeaderMissingIdError,
getSortInvalidDirectionError,
} from './sort-errors';
+import {coerceBooleanProperty} from '@angular/cdk/coercion';
/** Position of the arrow that displays when sorted. */
export type SortHeaderArrowPosition = 'before' | 'after';
@@ -82,6 +84,9 @@ export class MatSort extends _MatSortBase implements HasInitialized, OnChanges,
/** Collection of all registered sortables that this directive manages. */
sortables = new Map();
+ /** Map holding the sort state for each column */
+ sortState = new Map;
+
/** Used to notify any child components listening to state changes. */
readonly _stateChanges = new Subject();
@@ -112,6 +117,17 @@ export class MatSort extends _MatSortBase implements HasInitialized, OnChanges,
}
private _direction: SortDirection = '';
+ /** Whether to enable the multi-sorting feature */
+ @Input('matSortMultiple')
+ get matSortMultiple(): boolean {
+ return this._sortMultiple;
+ }
+
+ set matSortMultiple(value: any) {
+ this._sortMultiple = coerceBooleanProperty(value);
+ }
+ private _sortMultiple = false;
+
/**
* Whether to disable the user from clearing the sort by finishing the sort direction cycle.
* May be overridden by the MatSortable's disable clear input.
@@ -162,14 +178,53 @@ export class MatSort extends _MatSortBase implements HasInitialized, OnChanges,
/** Sets the active sort id and determines the new sort direction. */
sort(sortable: MatSortable): void {
+ let sortableDirection;
+ if (!this.isActive(sortable.id)) {
+ sortableDirection = sortable.start ?? this.start;
+ } else {
+ sortableDirection = this.getNextSortDirection(sortable);
+ }
+
+ // avoid keeping multiple sorts if not required.
+ if (!this._sortMultiple) {
+ this.sortState.clear();
+ }
+
+ // Update active and direction to keep backwards compatibility
if (this.active != sortable.id) {
this.active = sortable.id;
- this.direction = sortable.start ? sortable.start : this.start;
+ }
+ this.direction = sortableDirection;
+
+ const currentSort: Sort = {
+ active: sortable.id,
+ direction: sortableDirection,
+ };
+
+ // When unsorted, remove from state
+ if (sortableDirection !== '') {
+ this.sortState.set(sortable.id, currentSort);
} else {
- this.direction = this.getNextSortDirection(sortable);
+ this.sortState.delete(sortable.id);
}
- this.sortChange.emit({active: this.active, direction: this.direction});
+ this.sortChange.emit(currentSort);
+ }
+
+ /**
+ * Checks whether the provided column is currently active (has been sorted)
+ */
+ isActive(id: string): boolean {
+ return this.sortState.has(id);
+ }
+
+ /**
+ * Returns the current SortDirection of the supplied column id, defaults to unsorted if no state is found.
+ */
+ getCurrentSortDirection(id: string): SortDirection {
+ return this.sortState.get(id)?.direction
+ ?? this.sortables.get(id)?.start
+ ?? this.start;
}
/** Returns the next sort direction of the active sortable, checking for potential overrides. */
@@ -178,13 +233,14 @@ export class MatSort extends _MatSortBase implements HasInitialized, OnChanges,
return '';
}
+ const currentSortableDirection = this.getCurrentSortDirection(sortable.id);
// Get the sort direction cycle with the potential sortable overrides.
const disableClear =
sortable?.disableClear ?? this.disableClear ?? !!this._defaultOptions?.disableClear;
let sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear);
// Get and return the next direction in the cycle
- let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1;
+ let nextDirectionIndex = sortDirectionCycle.indexOf(currentSortableDirection) + 1;
if (nextDirectionIndex >= sortDirectionCycle.length) {
nextDirectionIndex = 0;
}
@@ -195,7 +251,24 @@ export class MatSort extends _MatSortBase implements HasInitialized, OnChanges,
this._markInitialized();
}
- ngOnChanges() {
+ ngOnChanges(changes: SimpleChanges) {
+ /* Update sortState with updated active and direction values, otherwise sorting won't work */
+ if (changes['active'] || changes['direction']) {
+ const currentActive = changes['active']?.currentValue ?? this.active;
+ const currentDirection = changes['direction']?.currentValue ?? this.direction ?? this.start;
+
+
+ // Handle sort deactivation
+ if ((!currentActive || currentActive === '') && changes['active']?.previousValue) {
+ this.sortState.delete(changes['active'].previousValue);
+ } else {
+ this.sortState.set(currentActive, {
+ active: currentActive,
+ direction: currentDirection,
+ } as Sort);
+ }
+ }
+
this._stateChanges.next();
}
diff --git a/src/material/table/table-data-source.ts b/src/material/table/table-data-source.ts
index 02330718e9b9..b6f4b5a81f5b 100644
--- a/src/material/table/table-data-source.ts
+++ b/src/material/table/table-data-source.ts
@@ -171,50 +171,65 @@ export class MatTableDataSource extend
* @param sort The connected MatSort that holds the current sort state.
*/
sortData: (data: T[], sort: MatSort) => T[] = (data: T[], sort: MatSort): T[] => {
- const active = sort.active;
- const direction = sort.direction;
- if (!active || direction == '') {
+ const sortState = Array.from(sort.sortState.values());
+
+ if (!sortState.length) {
return data;
}
- return data.sort((a, b) => {
- let valueA = this.sortingDataAccessor(a, active);
- let valueB = this.sortingDataAccessor(b, active);
-
- // If there are data in the column that can be converted to a number,
- // it must be ensured that the rest of the data
- // is of the same type so as not to order incorrectly.
- const valueAType = typeof valueA;
- const valueBType = typeof valueB;
-
- if (valueAType !== valueBType) {
- if (valueAType === 'number') {
- valueA += '';
- }
- if (valueBType === 'number') {
- valueB += '';
- }
- }
+ console.log(data, sortState)
- // If both valueA and valueB exist (truthy), then compare the two. Otherwise, check if
- // one value exists while the other doesn't. In this case, existing value should come last.
- // This avoids inconsistent results when comparing values to undefined/null.
- // If neither value exists, return 0 (equal).
- let comparatorResult = 0;
- if (valueA != null && valueB != null) {
- // Check if one value is greater than the other; if equal, comparatorResult should remain 0.
- if (valueA > valueB) {
- comparatorResult = 1;
- } else if (valueA < valueB) {
- comparatorResult = -1;
- }
- } else if (valueA != null) {
- comparatorResult = 1;
- } else if (valueB != null) {
- comparatorResult = -1;
- }
-
- return comparatorResult * (direction == 'asc' ? 1 : -1);
+ return data.sort((a, b) => {
+ return sortState
+ // Skip unsorted columns
+ .filter(it => it.direction !== '')
+
+ // Apply sorting to each 'sorted' column, consider next column only if previous resulted in sort == 0
+ .reduce((previous, s) => {
+
+ // We only calculate next column sort if previous sorting was 0
+ if (previous !== 0) {
+ return previous;
+ }
+
+ let valueA = this.sortingDataAccessor(a, s.active);
+ let valueB = this.sortingDataAccessor(b, s.active);
+
+ // If there are data in the column that can be converted to a r,
+ // it must be ensured that the rest of the data
+ // is of the same type so as not to order incorrectly.
+ const valueAType = typeof valueA;
+ const valueBType = typeof valueB;
+
+ if (valueAType !== valueBType) {
+ if (valueAType === 'number') {
+ valueA += '';
+ }
+ if (valueBType === 'number') {
+ valueB += '';
+ }
+ }
+
+ // If both valueA and valueB exist (truthy), then compare the two. Otherwise, check if
+ // one value exists while the other doesn't. In this case, existing value should come last.
+ // This avoids inconsistent results when comparing values to undefined/null.
+ // If neither value exists, return 0 (equal).
+ let comparatorResult = 0;
+ if (valueA != null && valueB != null) {
+ // Check if one value is greater than the other; if equal, comparatorResult should remain 0.
+ if (valueA > valueB) {
+ comparatorResult = 1;
+ } else if (valueA < valueB) {
+ comparatorResult = -1;
+ }
+ } else if (valueA != null) {
+ comparatorResult = 1;
+ } else if (valueB != null) {
+ comparatorResult = -1;
+ }
+
+ return comparatorResult * (s.direction === 'asc' ? 1 : -1);
+ }, 0);
});
};
@@ -271,10 +286,10 @@ export class MatTableDataSource extend
: observableOf(null);
const pageChange: Observable = this._paginator
? (merge(
- this._paginator.page,
- this._internalPageChanges,
- this._paginator.initialized,
- ) as Observable)
+ this._paginator.page,
+ this._internalPageChanges,
+ this._paginator.initialized,
+ ) as Observable)
: observableOf(null);
const dataStream = this._data;
// Watch for base data or filter changes to provide a filtered set of data.
diff --git a/src/material/table/table.spec.ts b/src/material/table/table.spec.ts
index 1ca2060e4b9b..2fbe97f999c7 100644
--- a/src/material/table/table.spec.ts
+++ b/src/material/table/table.spec.ts
@@ -10,7 +10,7 @@ import {
import {MatTable, MatTableDataSource, MatTableModule} from './index';
import {DataSource} from '@angular/cdk/table';
import {BehaviorSubject, Observable} from 'rxjs';
-import {MatSort, MatSortHeader, MatSortModule} from '@angular/material/sort';
+import {MatSort, MatSortable, MatSortHeader, MatSortModule} from '@angular/material/sort';
import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -194,6 +194,20 @@ describe('MDC-based MatTable', () => {
]);
});
+ it('should render with MatTableDataSource and multi-sort', () => {
+ let fixture = TestBed.createComponent(MatTableWithMultiSortApp);
+ fixture.detectChanges();
+
+ const tableElement = fixture.nativeElement.querySelector('table')!;
+ const data = fixture.componentInstance.dataSource!.data;
+ expectTableToMatchContent(tableElement, [
+ ['Column A', 'Column B', 'Column C'],
+ [data[0].a, data[0].b, data[0].c],
+ [data[1].a, data[1].b, data[1].c],
+ [data[2].a, data[2].b, data[2].c],
+ ]);
+ });
+
it('should render with MatTableDataSource and pagination', () => {
let fixture = TestBed.createComponent(MatTableWithPaginatorApp);
fixture.detectChanges();
@@ -247,6 +261,9 @@ describe('MDC-based MatTable', () => {
tableElement = fixture.nativeElement.querySelector('table');
component = fixture.componentInstance;
dataSource = fixture.componentInstance.dataSource;
+
+ component.sort.matSortMultiple = false;
+ component.sort.sortState.clear();
}));
it('should create table and display data source contents', () => {
@@ -437,7 +454,8 @@ describe('MDC-based MatTable', () => {
return '';
}
};
- component.sort.direction = '';
+
+ component.sort.sortState.clear();
component.sort.sort(component.sortHeader);
expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
@@ -632,6 +650,85 @@ describe('MDC-based MatTable', () => {
]);
});
});
+
+ describe('with MatTableDataSource and multi-sort', () => {
+ let tableElement: HTMLElement;
+ let fixture: ComponentFixture;
+ let dataSource: MatTableDataSource;
+ let component: ArrayDataSourceMatTableApp;
+
+ beforeEach(fakeAsync(() => {
+ fixture = TestBed.createComponent(ArrayDataSourceMatTableApp);
+ fixture.detectChanges();
+
+ tableElement = fixture.nativeElement.querySelector('table');
+ component = fixture.componentInstance;
+ dataSource = fixture.componentInstance.dataSource;
+
+ dataSource.data = [
+ { a: "a_3", b: "b_1", c: "c_1" },
+ { a: "a_2", b: "b_2", c: "c_1" },
+ { a: "a_1", b: "b_3", c: "c_2" },
+ ];
+
+ component.sort.matSortMultiple = true;
+ component.sort.sortState.clear();
+ }));
+
+ it('should be able to sort the table contents base on multiple columns at same time', () => {
+
+ const columnA = { id: 'a' } as MatSortable;
+ const columnB = { id: 'b' } as MatSortable;
+ const columnC = { id: 'c' } as MatSortable;
+
+ // Activate column A sort
+ component.sort.sort(columnA);
+ fixture.detectChanges();
+ expectTableToMatchContent(tableElement, [
+ ['Column A', 'Column B', 'Column C'],
+ ['a_1', 'b_3', 'c_2'],
+ ['a_2', 'b_2', 'c_1'],
+ ['a_3', 'b_1', 'c_1'],
+ ['Footer A', 'Footer B', 'Footer C'],
+ ]);
+
+ // reset sorting state
+ component.sort.sortState.clear();
+
+ // Activate column C sort
+ component.sort.sort(columnC);
+ fixture.detectChanges();
+ expectTableToMatchContent(tableElement, [
+ ['Column A', 'Column B', 'Column C'],
+ ['a_3', 'b_1', 'c_1'],
+ ['a_2', 'b_2', 'c_1'],
+ ['a_1', 'b_3', 'c_2'],
+ ['Footer A', 'Footer B', 'Footer C'],
+ ]);
+
+ // Activate column B sort
+ component.sort.sort(columnB);
+ fixture.detectChanges();
+ expectTableToMatchContent(tableElement, [
+ ['Column A', 'Column B', 'Column C'],
+ ['a_3', 'b_1', 'c_1'],
+ ['a_2', 'b_2', 'c_1'],
+ ['a_1', 'b_3', 'c_2'],
+ ['Footer A', 'Footer B', 'Footer C'],
+ ]);
+
+ // Activate column B sort again (reverse)
+ component.sort.sort(columnB);
+ fixture.detectChanges();
+ expectTableToMatchContent(tableElement, [
+ ['Column A', 'Column B', 'Column C'],
+ ['a_2', 'b_2', 'c_1'],
+ ['a_3', 'b_1', 'c_1'],
+ ['a_1', 'b_3', 'c_2'],
+ ['Footer A', 'Footer B', 'Footer C'],
+ ]);
+ });
+ })
});
interface TestData {
@@ -714,7 +811,7 @@ class FakeDataSource extends DataSource {
imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class MatTableApp {
- dataSource: FakeDataSource | null = new FakeDataSource();
+ dataSource: FakeDataSource = new FakeDataSource();
columnsToRender = ['column_a', 'column_b', 'column_c'];
isFourthRow = (i: number, _rowData: TestData) => i == 3;
@@ -750,7 +847,7 @@ class MatTableApp {
imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class NativeHtmlTableApp {
- dataSource: FakeDataSource | null = new FakeDataSource();
+ dataSource: FakeDataSource = new FakeDataSource();
columnsToRender = ['column_a', 'column_b', 'column_c'];
@ViewChild(MatTable) table: MatTable;
@@ -805,7 +902,7 @@ class NativeHtmlTableApp {
imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class NestedTableApp {
- dataSource: FakeDataSource | null = new FakeDataSource();
+ dataSource: FakeDataSource = new FakeDataSource();
columnsToRender = ['column_a', 'column_b', 'column_c'];
}
@@ -855,7 +952,7 @@ class StickyTableApp {
})
class MatTableWithWhenRowApp {
multiTemplateDataRows = false;
- dataSource: FakeDataSource | null = new FakeDataSource();
+ dataSource: FakeDataSource = new FakeDataSource();
isFourthRow = (i: number, _rowData: TestData) => i == 3;
@ViewChild(MatTable) table: MatTable;
@@ -972,6 +1069,57 @@ class MatTableWithSortApp implements OnInit {
}
}
+@Component({
+ template: `
+
+
+ Column A |
+ {{row.a}} |
+
+
+
+ Column B |
+ {{row.b}} |
+
+
+
+ Column C |
+ {{row.c}} |
+
+
+
+
+
+ `,
+ standalone: true,
+ imports: [MatTableModule, MatPaginatorModule, MatSortModule],
+})
+class MatTableWithMultiSortApp implements OnInit {
+ underlyingDataSource = new FakeDataSource();
+ dataSource = new MatTableDataSource();
+ columnsToRender = ['column_a', 'column_b', 'column_c'];
+
+ @ViewChild(MatTable) table: MatTable;
+ @ViewChild(MatSort) sort: MatSort;
+
+ constructor() {
+ this.underlyingDataSource.data = [];
+
+ // Add three rows of data
+ this.underlyingDataSource.addData();
+ this.underlyingDataSource.addData();
+ this.underlyingDataSource.addData();
+
+ this.underlyingDataSource.connect().subscribe(data => {
+ this.dataSource.data = data;
+ });
+ }
+
+ ngOnInit() {
+ this.dataSource!.sort = this.sort;
+ }
+}
+
@Component({
template: `
@@ -1043,7 +1191,7 @@ class MatTableWithPaginatorApp implements OnInit {
imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class TableWithNgContainerRow {
- dataSource: FakeDataSource | null = new FakeDataSource();
+ dataSource: FakeDataSource = new FakeDataSource();
columnsToRender = ['column_a'];
}
@@ -1082,7 +1230,7 @@ class TableWithNgContainerRow {
imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class MatFlexTableApp {
- dataSource: FakeDataSource | null = new FakeDataSource();
+ dataSource: FakeDataSource = new FakeDataSource();
columnsToRender = ['column_a', 'column_b', 'column_c'];
@ViewChild(MatTable) table: MatTable;
}
diff --git a/tools/public_api_guard/material/sort.md b/tools/public_api_guard/material/sort.md
index b63acfbd56f5..6082e6e36a26 100644
--- a/tools/public_api_guard/material/sort.md
+++ b/tools/public_api_guard/material/sort.md
@@ -19,6 +19,7 @@ import { OnChanges } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { OnInit } from '@angular/core';
import { Optional } from '@angular/core';
+import { SimpleChanges } from '@angular/core';
import { Subject } from 'rxjs';
// @public
@@ -54,13 +55,17 @@ export class MatSort extends _MatSortBase implements HasInitialized, OnChanges,
set direction(direction: SortDirection);
disableClear: boolean;
disabled: boolean;
+ getCurrentSortDirection(id: string): SortDirection;
getNextSortDirection(sortable: MatSortable): SortDirection;
+ isActive(id: string): boolean;
+ get matSortMultiple(): boolean;
+ set matSortMultiple(value: any);
// (undocumented)
static ngAcceptInputType_disableClear: unknown;
// (undocumented)
static ngAcceptInputType_disabled: unknown;
// (undocumented)
- ngOnChanges(): void;
+ ngOnChanges(changes: SimpleChanges): void;
// (undocumented)
ngOnDestroy(): void;
// (undocumented)
@@ -69,10 +74,11 @@ export class MatSort extends _MatSortBase implements HasInitialized, OnChanges,
sort(sortable: MatSortable): void;
sortables: Map;
readonly sortChange: EventEmitter;
+ sortState: Map;
start: SortDirection;
readonly _stateChanges: Subject;
// (undocumented)
- static ɵdir: i0.ɵɵDirectiveDeclaration;
+ static ɵdir: i0.ɵɵDirectiveDeclaration;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration;
}