mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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'],
|
||||
|
||||
Reference in New Issue
Block a user