From 7c4b3949d54547ef17de412ffd362c3a545f457b Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 1 Nov 2017 20:34:26 -0700 Subject: [PATCH] feat(table): add access to filtered data in MatTableDataSource (#7912) * feat(table): add access to filtered data in MatTableDataSource * add more test --- src/demo-app/table/table-demo.html | 37 ++++++++++++++++++---- src/demo-app/table/table-demo.scss | 24 +++++++++++++- src/demo-app/table/table-demo.ts | 51 +++++++++++++++++++++++++++--- src/lib/table/table-data-source.ts | 14 ++++++-- src/lib/table/table.spec.ts | 9 ++++++ 5 files changed, 121 insertions(+), 14 deletions(-) diff --git a/src/demo-app/table/table-demo.html b/src/demo-app/table/table-demo.html index 0433ee7f1ad..2b5cf19f3eb 100644 --- a/src/demo-app/table/table-demo.html +++ b/src/demo-app/table/table-demo.html @@ -214,18 +214,41 @@

MatTable With MatTableDataSource Example

- + -
+
+ (toggleColorColumn)="toggleColorColumn()" *ngIf="selection.isEmpty()"> +
+ {{selection.selected.length}} + {{selection.selected.length == 1 ? 'user' : 'users'}} + selected +
+ + + + + + + + + + + + ID @@ -258,9 +281,11 @@ {{row.color}} - - - + + + (); wasExpanded = new Set(); - filter = new FormControl(); + matTableDataSourceColumns = ['select', 'userId', 'userName', 'progress', 'color']; + selection = new SelectionModel(true, []); + + @ViewChild('filter') filter: ElementRef; dynamicColumnDefs: any[] = []; dynamicColumnIds: string[] = []; @@ -62,7 +69,6 @@ export class TableDemo { }; this.matTableDataSource.filterPredicate = (data: UserData, filter: string) => data.name.indexOf(filter) != -1; - this.filter.valueChanges.subscribe(filter => this.matTableDataSource!.filter = filter); } ngAfterViewInit() { @@ -74,6 +80,43 @@ export class TableDemo { ngOnInit() { this.connect(); + Observable.fromEvent(this.filter.nativeElement, 'keyup') + .debounceTime(150) + .distinctUntilChanged() + .subscribe(() => { + this.paginatorForDataSource.pageIndex = 0; + this.matTableDataSource.filter = this.filter.nativeElement.value; + }); + } + + /** Whether all filtered rows are selected. */ + isAllFilteredRowsSelected() { + return this.matTableDataSource.filteredData.every(data => this.selection.isSelected(data)); + } + + /** Whether the selection it totally matches the filtered rows. */ + isMasterToggleChecked() { + return this.selection.hasValue() && + this.isAllFilteredRowsSelected() && + this.selection.selected.length >= this.matTableDataSource.filteredData.length; + } + + /** + * Whether there is a selection that doesn't capture all the + * filtered rows there are no filtered rows displayed. + */ + isMasterToggleIndeterminate() { + return this.selection.hasValue() && + (!this.isAllFilteredRowsSelected() || !this.matTableDataSource.filteredData.length); + } + + /** Selects all filtered rows if they are not all selected; otherwise clear selection. */ + masterToggle() { + if (this.isMasterToggleChecked()) { + this.selection.clear(); + } else { + this.matTableDataSource.filteredData.forEach(data => this.selection.select(data)); + } } addDynamicColumnDef() { diff --git a/src/lib/table/table-data-source.ts b/src/lib/table/table-data-source.ts index 98fbd4b82ba..f45e427afff 100644 --- a/src/lib/table/table-data-source.ts +++ b/src/lib/table/table-data-source.ts @@ -40,6 +40,14 @@ export class MatTableDataSource implements DataSource { */ _renderChangesSubscription: Subscription; + /** + * The filtered set of data that has been matched by the filter string, or all the data if there + * is no filter. Useful for knowing the set of data the table represents. + * For example, a 'selectAll()' function would likely want to select the set of filtered data + * shown to the user rather than all the data. + */ + filteredData: T[]; + /** Array of data that should be rendered by the table, where each object represents one row. */ set data(data: T[]) { this._data.next(data); } get data() { return this._data.value; } @@ -164,12 +172,12 @@ export class MatTableDataSource implements DataSource { // If there is a filter string, filter out data that does not contain it. // Each data object is converted to a string using the function defined by filterTermAccessor. // May be overriden for customization. - const filteredData = + this.filteredData = !this.filter ? data : data.filter(obj => this.filterPredicate(obj, this.filter)); - if (this.paginator) { this._updatePaginator(filteredData.length); } + if (this.paginator) { this._updatePaginator(this.filteredData.length); } - return filteredData; + return this.filteredData; } /** diff --git a/src/lib/table/table.spec.ts b/src/lib/table/table.spec.ts index b4b398f3230..2b2f1e360f6 100644 --- a/src/lib/table/table.spec.ts +++ b/src/lib/table/table.spec.ts @@ -102,16 +102,21 @@ describe('MatTable', () => { // Change filter to a_1, should match one row dataSource.filter = 'a_1'; fixture.detectChanges(); + expect(dataSource.filteredData.length).toBe(1); + expect(dataSource.filteredData[0]).toBe(dataSource.data[0]); expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], ['a_1', 'b_1', 'c_1'], ]); + flushMicrotasks(); // Resolve promise that updates paginator's length expect(dataSource.paginator!.length).toBe(1); // Change filter to ' A_2 ', should match one row (ignores case and whitespace) dataSource.filter = ' A_2 '; fixture.detectChanges(); + expect(dataSource.filteredData.length).toBe(1); + expect(dataSource.filteredData[0]).toBe(dataSource.data[1]); expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], ['a_2', 'b_2', 'c_2'], @@ -120,6 +125,10 @@ describe('MatTable', () => { // Change filter to empty string, should match all rows dataSource.filter = ''; fixture.detectChanges(); + expect(dataSource.filteredData.length).toBe(3); + expect(dataSource.filteredData[0]).toBe(dataSource.data[0]); + expect(dataSource.filteredData[1]).toBe(dataSource.data[1]); + expect(dataSource.filteredData[2]).toBe(dataSource.data[2]); expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], ['a_1', 'b_1', 'c_1'],