From 572a6f250fe92f723783ac57c8b1e8a443a1bb67 Mon Sep 17 00:00:00 2001 From: Israel Merljak Date: Sun, 21 Jan 2024 02:47:07 +0000 Subject: [PATCH] feat(components/sort): add multi-sort support Adds multi-column sorting capability to MatSort, allowing to sort a table on multiple of its columns at once by toggling matSortMultiple. This feature adds a new sortState variable inside MatSort that should be used as a source of truth when matSortMultiple is enabled. Fixes #24102 --- .../table-sorting/table-sorting-example.css | 4 + .../table-sorting/table-sorting-example.html | 40 +++-- .../table-sorting/table-sorting-example.ts | 49 +++--- src/material/sort/sort-header.ts | 11 +- src/material/sort/sort.spec.ts | 21 +++ src/material/sort/sort.ts | 85 ++++++++- src/material/table/table-data-source.ts | 103 ++++++----- src/material/table/table.spec.ts | 164 +++++++++++++++++- tools/public_api_guard/material/sort.md | 10 +- 9 files changed, 391 insertions(+), 96 deletions(-) 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 + +
+ - + - + - + - + - + + + + + + + - + - + - + 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: ` +
- 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}}
+ + + + + + + + + + + + + + + + + +
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; }