Skip to content

Commit 7c4b394

Browse files
authored
feat(table): add access to filtered data in MatTableDataSource (#7912)
* feat(table): add access to filtered data in MatTableDataSource * add more test
1 parent c0ba25a commit 7c4b394

File tree

5 files changed

+121
-14
lines changed

5 files changed

+121
-14
lines changed

src/demo-app/table/table-demo.html

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,18 +214,41 @@ <h3> MatTable Using 'When' Rows for Interactive Details</h3>
214214
<h3>MatTable With MatTableDataSource Example</h3>
215215

216216
<mat-form-field>
217-
<input matInput [formControl]="filter">
217+
<input matInput #filter placeholder="Filter users">
218218
</mat-form-field>
219219

220-
<div class="demo-table-container demo-mat-table-example mat-elevation-z4">
220+
<div class="demo-table-container demo-mat-table-example mat-elevation-z4 mat-table-selectable">
221221

222222
<table-header-demo (shiftColumns)="displayedColumns.push(displayedColumns.shift())"
223-
(toggleColorColumn)="toggleColorColumn()">
223+
(toggleColorColumn)="toggleColorColumn()" *ngIf="selection.isEmpty()">
224224
</table-header-demo>
225+
<div class="example-header example-selection-header"
226+
*ngIf="!selection.isEmpty()">
227+
{{selection.selected.length}}
228+
{{selection.selected.length == 1 ? 'user' : 'users'}}
229+
selected
230+
</div>
225231

226232
<mat-table [dataSource]="matTableDataSource" [trackBy]="userTrackBy" matSort
227233
#sortForDataSource="matSort">
228234

235+
<!-- Checkbox Column -->
236+
<ng-container matColumnDef="select">
237+
<mat-header-cell *matHeaderCellDef>
238+
<mat-checkbox (change)="$event ? masterToggle() : null"
239+
[disabled]="!matTableDataSource.filteredData.length"
240+
[checked]="isMasterToggleChecked()"
241+
[indeterminate]="isMasterToggleIndeterminate()">
242+
</mat-checkbox>
243+
</mat-header-cell>
244+
<mat-cell *matCellDef="let row">
245+
<mat-checkbox (click)="$event.stopPropagation()"
246+
(change)="$event ? selection.toggle(row) : null"
247+
[checked]="selection.isSelected(row)">
248+
</mat-checkbox>
249+
</mat-cell>
250+
</ng-container>
251+
229252
<!-- Column Definition: ID -->
230253
<ng-container cdkColumnDef="userId">
231254
<mat-header-cell *matHeaderCellDef mat-sort-header> ID </mat-header-cell>
@@ -258,9 +281,11 @@ <h3>MatTable With MatTableDataSource Example</h3>
258281
<mat-cell *matCellDef="let row" [style.color]="row.color"> {{row.color}} </mat-cell>
259282
</ng-container>
260283

261-
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
262-
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
263-
284+
<mat-header-row *matHeaderRowDef="matTableDataSourceColumns"></mat-header-row>
285+
<mat-row *matRowDef="let row; columns: matTableDataSourceColumns;"
286+
[class.selected]="selection.isSelected(row)"
287+
(click)="selection.toggle(row)">
288+
</mat-row>
264289
</mat-table>
265290

266291
<mat-paginator #paginatorForDataSource

src/demo-app/table/table-demo.scss

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
}
7474
}
7575

76-
/* Structure so that the table is contained on a card */
76+
/* Structure so that the table is contained on a card */
7777
.demo-table-container {
7878
max-height: 800px;
7979
display: flex;
@@ -98,6 +98,11 @@
9898
max-width: 160px;
9999
}
100100

101+
.mat-column-select {
102+
max-width: 48px;
103+
overflow: inherit;
104+
}
105+
101106
/* Progress bar styling */
102107
.cdk-column-progress {
103108
&.cdk-cell, &.mat-cell {
@@ -119,3 +124,20 @@
119124
}
120125
}
121126
}
127+
128+
.example-selection-header {
129+
height: 64px;
130+
background: white;
131+
display: flex;
132+
align-items: center;
133+
padding-left: 24px;
134+
}
135+
136+
.mat-table-selectable {
137+
.mat-row:hover {
138+
background: #eeeeee;
139+
}
140+
.mat-row.selected {
141+
background: #f5f5f5;
142+
}
143+
}

src/demo-app/table/table-demo.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import {Component, ViewChild} from '@angular/core';
1+
import {Component, ElementRef, ViewChild} from '@angular/core';
22
import {PeopleDatabase, UserData} from './people-database';
33
import {PersonDataSource} from './person-data-source';
44
import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
55
import {DetailRow, PersonDetailDataSource} from './person-detail-data-source';
66
import {animate, state, style, transition, trigger} from '@angular/animations';
7-
import {FormControl} from '@angular/forms';
7+
import {SelectionModel} from '@angular/cdk/collections';
8+
import {Observable} from 'rxjs/Observable';
9+
import 'rxjs/add/operator/distinctUntilChanged';
10+
import 'rxjs/add/operator/debounceTime';
11+
import 'rxjs/add/observable/fromEvent';
812

913
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined;
1014

@@ -35,7 +39,10 @@ export class TableDemo {
3539
highlights = new Set<string>();
3640
wasExpanded = new Set<UserData>();
3741

38-
filter = new FormControl();
42+
matTableDataSourceColumns = ['select', 'userId', 'userName', 'progress', 'color'];
43+
selection = new SelectionModel<UserData>(true, []);
44+
45+
@ViewChild('filter') filter: ElementRef;
3946

4047
dynamicColumnDefs: any[] = [];
4148
dynamicColumnIds: string[] = [];
@@ -62,7 +69,6 @@ export class TableDemo {
6269
};
6370
this.matTableDataSource.filterPredicate =
6471
(data: UserData, filter: string) => data.name.indexOf(filter) != -1;
65-
this.filter.valueChanges.subscribe(filter => this.matTableDataSource!.filter = filter);
6672
}
6773

6874
ngAfterViewInit() {
@@ -74,6 +80,43 @@ export class TableDemo {
7480

7581
ngOnInit() {
7682
this.connect();
83+
Observable.fromEvent(this.filter.nativeElement, 'keyup')
84+
.debounceTime(150)
85+
.distinctUntilChanged()
86+
.subscribe(() => {
87+
this.paginatorForDataSource.pageIndex = 0;
88+
this.matTableDataSource.filter = this.filter.nativeElement.value;
89+
});
90+
}
91+
92+
/** Whether all filtered rows are selected. */
93+
isAllFilteredRowsSelected() {
94+
return this.matTableDataSource.filteredData.every(data => this.selection.isSelected(data));
95+
}
96+
97+
/** Whether the selection it totally matches the filtered rows. */
98+
isMasterToggleChecked() {
99+
return this.selection.hasValue() &&
100+
this.isAllFilteredRowsSelected() &&
101+
this.selection.selected.length >= this.matTableDataSource.filteredData.length;
102+
}
103+
104+
/**
105+
* Whether there is a selection that doesn't capture all the
106+
* filtered rows there are no filtered rows displayed.
107+
*/
108+
isMasterToggleIndeterminate() {
109+
return this.selection.hasValue() &&
110+
(!this.isAllFilteredRowsSelected() || !this.matTableDataSource.filteredData.length);
111+
}
112+
113+
/** Selects all filtered rows if they are not all selected; otherwise clear selection. */
114+
masterToggle() {
115+
if (this.isMasterToggleChecked()) {
116+
this.selection.clear();
117+
} else {
118+
this.matTableDataSource.filteredData.forEach(data => this.selection.select(data));
119+
}
77120
}
78121

79122
addDynamicColumnDef() {

src/lib/table/table-data-source.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ export class MatTableDataSource<T> implements DataSource<T> {
4040
*/
4141
_renderChangesSubscription: Subscription;
4242

43+
/**
44+
* The filtered set of data that has been matched by the filter string, or all the data if there
45+
* is no filter. Useful for knowing the set of data the table represents.
46+
* For example, a 'selectAll()' function would likely want to select the set of filtered data
47+
* shown to the user rather than all the data.
48+
*/
49+
filteredData: T[];
50+
4351
/** Array of data that should be rendered by the table, where each object represents one row. */
4452
set data(data: T[]) { this._data.next(data); }
4553
get data() { return this._data.value; }
@@ -164,12 +172,12 @@ export class MatTableDataSource<T> implements DataSource<T> {
164172
// If there is a filter string, filter out data that does not contain it.
165173
// Each data object is converted to a string using the function defined by filterTermAccessor.
166174
// May be overriden for customization.
167-
const filteredData =
175+
this.filteredData =
168176
!this.filter ? data : data.filter(obj => this.filterPredicate(obj, this.filter));
169177

170-
if (this.paginator) { this._updatePaginator(filteredData.length); }
178+
if (this.paginator) { this._updatePaginator(this.filteredData.length); }
171179

172-
return filteredData;
180+
return this.filteredData;
173181
}
174182

175183
/**

src/lib/table/table.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,21 @@ describe('MatTable', () => {
102102
// Change filter to a_1, should match one row
103103
dataSource.filter = 'a_1';
104104
fixture.detectChanges();
105+
expect(dataSource.filteredData.length).toBe(1);
106+
expect(dataSource.filteredData[0]).toBe(dataSource.data[0]);
105107
expectTableToMatchContent(tableElement, [
106108
['Column A', 'Column B', 'Column C'],
107109
['a_1', 'b_1', 'c_1'],
108110
]);
111+
109112
flushMicrotasks(); // Resolve promise that updates paginator's length
110113
expect(dataSource.paginator!.length).toBe(1);
111114

112115
// Change filter to ' A_2 ', should match one row (ignores case and whitespace)
113116
dataSource.filter = ' A_2 ';
114117
fixture.detectChanges();
118+
expect(dataSource.filteredData.length).toBe(1);
119+
expect(dataSource.filteredData[0]).toBe(dataSource.data[1]);
115120
expectTableToMatchContent(tableElement, [
116121
['Column A', 'Column B', 'Column C'],
117122
['a_2', 'b_2', 'c_2'],
@@ -120,6 +125,10 @@ describe('MatTable', () => {
120125
// Change filter to empty string, should match all rows
121126
dataSource.filter = '';
122127
fixture.detectChanges();
128+
expect(dataSource.filteredData.length).toBe(3);
129+
expect(dataSource.filteredData[0]).toBe(dataSource.data[0]);
130+
expect(dataSource.filteredData[1]).toBe(dataSource.data[1]);
131+
expect(dataSource.filteredData[2]).toBe(dataSource.data[2]);
123132
expectTableToMatchContent(tableElement, [
124133
['Column A', 'Column B', 'Column C'],
125134
['a_1', 'b_1', 'c_1'],

0 commit comments

Comments
 (0)