feat(table): add access to filtered data in MatTableDataSource (#7912)

* feat(table): add access to filtered data in MatTableDataSource

* add more test
This commit is contained in:
Andrew Seguin
2017-11-01 20:34:26 -07:00
committed by GitHub
parent c0ba25a0e8
commit 7c4b3949d5
5 changed files with 121 additions and 14 deletions
+31 -6
View File
@@ -214,18 +214,41 @@
<h3>MatTable With MatTableDataSource Example</h3>
<mat-form-field>
<input matInput [formControl]="filter">
<input matInput #filter placeholder="Filter users">
</mat-form-field>
<div class="demo-table-container demo-mat-table-example mat-elevation-z4">
<div class="demo-table-container demo-mat-table-example mat-elevation-z4 mat-table-selectable">
<table-header-demo (shiftColumns)="displayedColumns.push(displayedColumns.shift())"
(toggleColorColumn)="toggleColorColumn()">
(toggleColorColumn)="toggleColorColumn()" *ngIf="selection.isEmpty()">
</table-header-demo>
<div class="example-header example-selection-header"
*ngIf="!selection.isEmpty()">
{{selection.selected.length}}
{{selection.selected.length == 1 ? 'user' : 'users'}}
selected
</div>
<mat-table [dataSource]="matTableDataSource" [trackBy]="userTrackBy" matSort
#sortForDataSource="matSort">
<!-- Checkbox Column -->
<ng-container matColumnDef="select">
<mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="$event ? masterToggle() : null"
[disabled]="!matTableDataSource.filteredData.length"
[checked]="isMasterToggleChecked()"
[indeterminate]="isMasterToggleIndeterminate()">
</mat-checkbox>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</mat-cell>
</ng-container>
<!-- Column Definition: ID -->
<ng-container cdkColumnDef="userId">
<mat-header-cell *matHeaderCellDef mat-sort-header> ID </mat-header-cell>
@@ -258,9 +281,11 @@
<mat-cell *matCellDef="let row" [style.color]="row.color"> {{row.color}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
<mat-header-row *matHeaderRowDef="matTableDataSourceColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: matTableDataSourceColumns;"
[class.selected]="selection.isSelected(row)"
(click)="selection.toggle(row)">
</mat-row>
</mat-table>
<mat-paginator #paginatorForDataSource
+23 -1
View File
@@ -73,7 +73,7 @@
}
}
/* Structure so that the table is contained on a card */
/* Structure so that the table is contained on a card */
.demo-table-container {
max-height: 800px;
display: flex;
@@ -98,6 +98,11 @@
max-width: 160px;
}
.mat-column-select {
max-width: 48px;
overflow: inherit;
}
/* Progress bar styling */
.cdk-column-progress {
&.cdk-cell, &.mat-cell {
@@ -119,3 +124,20 @@
}
}
}
.example-selection-header {
height: 64px;
background: white;
display: flex;
align-items: center;
padding-left: 24px;
}
.mat-table-selectable {
.mat-row:hover {
background: #eeeeee;
}
.mat-row.selected {
background: #f5f5f5;
}
}
+47 -4
View File
@@ -1,10 +1,14 @@
import {Component, ViewChild} from '@angular/core';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {PeopleDatabase, UserData} from './people-database';
import {PersonDataSource} from './person-data-source';
import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
import {DetailRow, PersonDetailDataSource} from './person-detail-data-source';
import {animate, state, style, transition, trigger} from '@angular/animations';
import {FormControl} from '@angular/forms';
import {SelectionModel} from '@angular/cdk/collections';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/observable/fromEvent';
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined;
@@ -35,7 +39,10 @@ export class TableDemo {
highlights = new Set<string>();
wasExpanded = new Set<UserData>();
filter = new FormControl();
matTableDataSourceColumns = ['select', 'userId', 'userName', 'progress', 'color'];
selection = new SelectionModel<UserData>(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() {
+11 -3
View File
@@ -40,6 +40,14 @@ export class MatTableDataSource<T> implements DataSource<T> {
*/
_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<T> implements DataSource<T> {
// 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;
}
/**
+9
View File
@@ -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'],