Angular material table 多个过滤谓词
Angular material table multiple filterpredicate
各位。
我需要帮助。
我正在创建一个必须包含全局筛选器和列筛选器的数据透视表 table。
但是我编码的方式我不明白。
遵循 stackblitz
的代码和 link
<p>data-table-dynamic works!</p>
<mat-form-field *ngIf="filter">
<input
matInput
(keyup)="applyFilter($event)"
placeholder="{{ filterPlaceholder }}"
/>
</mat-form-field>
<div class="mat-elevation-z8">
<table
mat-table
[dataSource]="dataSource"
matSort
[ngStyle]="{ 'min-width': +tableMinWidth + 'px' }"
>
<ng-container *ngFor="let column of columns">
<ng-container matColumnDef="{{ column.columnDef }}">
<th class="header" mat-header-cell *matHeaderCellDef mat-sort-header>
<div fxFlexFill>
{{ column.header }}
</div>
</th>
<td mat-cell *matCellDef="let row">{{ column.cell(row) }}</td>
</ng-container>
</ng-container>
<ng-container *ngIf="buttons.length >= 0">
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>
<button
*ngIf="columnsFilter"
mat-icon-button
matTooltip="Toggle Filters"
(click)="toggleFilters = !toggleFilters"
>
<mat-icon>search</mat-icon>
</button>
</th>
<td
mat-cell
*matCellDef="let row"
[ngStyle]="{ 'min-width': 'calc(55px * ' + buttons.length + ')' }"
>
<div class="btn-group" *ngFor="let button of buttons">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
[matMenuTriggerData]="{ data: row }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</td>
</ng-container>
</ng-container>
<ng-container *ngFor="let column of columns; let i = index">
<ng-container matColumnDef="{{ column.columnSearch }}">
<th class="header" mat-header-cell *matHeaderCellDef>
<div
fxFlexFill
class="filters-container"
[class.animate]="toggleFilters"
>
<mat-form-field *ngIf="i >= 0" appearance="outline">
<input
matInput
placeholder="Press 'Enter' to search"
[(ngModel)]="filtersModel[i]"
(keyup)="searchColumns()"
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</div>
</th>
<td mat-cell *matCellDef="let row">{{ column.cell(row) }}</td>
</ng-container>
</ng-container>
<ng-container matColumnDef="filter" *ngIf="columnsFilter">
<th mat-header-cell *matHeaderCellDef class="filterHeaderCell">
<div class="filters-container" [class.animate]="toggleFilters">
<button
mat-icon-button
matTooltip="Clear Filters"
(click)="clearFilters()"
>
<mat-icon>search_off</mat-icon>
</button>
</div>
</th>
</ng-container>
<!-- Disclaimer column - with nullable approach -->
<ng-container matColumnDef="disclaimer" *ngIf="footer">
<td mat-footer-cell *matFooterCellDef colspan="100%">
<strong>{{ footer }}</strong>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<ng-container *ngIf="columnsFilter">
<tr
mat-header-row
*matHeaderRowDef="displayedColumnsSearch"
class="mat-header-filter"
></tr>
<!-- class="no-default-height" -->
</ng-container>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<!-- <tr mat-header-row *matHeaderRowDef="searchColumn"></tr> -->
<ng-container *ngIf="footer">
<!-- Make footer nullable -->
<tr
mat-footer-row
*matFooterRowDef="['disclaimer']"
class="second-footer-row"
></tr>
</ng-container>
</table>
<mat-paginator
[pageSizeOptions]="pagination"
[pageSize]="pageSize"
[ngStyle]="{ 'min-width': +tableMinWidth + 'px' }"
></mat-paginator>
<mat-menu #menu="matMenu">
<ng-template matMenuContent let-data="data">
<div *ngFor="let button of menuButtons">
<button
mat-menu-item
(click)="this.buttonClick.emit([button.action, button.payload(data)])"
>
<mat-icon>{{ button.icon }}</mat-icon>
{{ button.description }}
</button>
</div>
</ng-template>
</mat-menu>
</div>
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { TableBtn, TableColumn } from '../../core/interfaces';
import { TableMenu } from '../../core/interfaces/table-menu';
@Component({
selector: 'app-data-table-dynamic',
templateUrl: './data-table-dynamic.component.html',
styleUrls: ['./data-table-dynamic.component.scss'],
})
export class DataTableDynamicComponent implements OnChanges, OnInit {
@Input() columns: TableColumn[] = [];
@Input() buttons: TableBtn[] = [];
@Input() menuButtons: TableMenu[] = [];
@Input() data: any[] = [];
@Input() filter: boolean = false;
@Input() filterPlaceholder: string = 'Filter';
@Input() columnsFilter: boolean = false;
@Input() footer: string = null;
@Input() pagination: number[] = [];
@Input() pageSize: number;
@Input() tableMinWidth: number = 500;
@Output() filteredData = new EventEmitter<any[]>();
@Output() buttonClick = new EventEmitter<string[]>();
dataSource: MatTableDataSource<any>;
displayedColumns: string[];
displayedColumnsSearch: string[];
headers: string[] = this.columns.map((x) => x.columnDef);
headersFilters = this.headers.map((x, i) => x + '_' + i);
filtersModel = [];
filterKeys = {};
toggleFilters = true;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;
constructor() {}
ngOnInit(): void {}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
if (this.data) {
if (changes.data) {
this.dataSource = new MatTableDataSource(this.data);
this.dataSource.filterPredicate = (item, filter: string) => {
const colMatch = !Object.keys(this.filterKeys).reduce(
(remove, field) => {
return (
remove ||
!item[field]
.toString()
.toLocaleLowerCase()
.includes(this.filterKeys[field])
);
},
false
);
return colMatch;
};
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
this.displayedColumns = [...this.columns.map((c) => c.columnDef)];
this.displayedColumnsSearch = [
...this.columns.map((c) => c.columnSearch),
'filter',
];
this.columns.forEach((value, index) => {
this.filterKeys[this.columns[index].columnDef] = '';
});
if (this.buttons.length > 0)
this.displayedColumns = [...this.displayedColumns, 'actions'];
}
}
}
applyFilter(filterValue) {
this.dataSource.filter = filterValue.target.value.trim().toLowerCase();
this.filteredData.emit(this.dataSource.filteredData);
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
this.dataSource.sort = this.sort;
}
searchColumns() {
this.filtersModel.forEach((each, ind) => {
this.filterKeys[this.columns[ind].columnDef] = each || '';
});
//Call API with filters
this.dataSource.filter = JSON.stringify(this.filterKeys);
this.filteredData.emit(this.dataSource.filteredData);
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
this.dataSource.sort = this.sort;
}
clearFilters() {
this.filtersModel = [];
this.columns.forEach((value, index) => {
this.filterKeys[this.columns[index].columnDef] = '';
});
//Call API without filters
this.searchColumns();
}
}
我知道不是这样,但我做了一些测试,但我做不到。我把工作代码,所以你可以帮助我。
如果您有任何文章或示例,将会有很大帮助。
我以这段代码为例,并根据需要进行了调整。
好吧,我们可以使用列的值创建一个 FormGroup,为此 columns' is a property and we use the way
@Input('columns') set _(value)`
form: FormGroup = new FormGroup({});
columns: TableColumn[] = [];
@Input('columns') set _columns(value) {
this.columns = value;
value.forEach((x) => {
this.form.addControl(x.columnDef, new FormControl());
});
this.form.addControl('_general', new FormControl());
this.form.valueChanges.subscribe((res) => {
this.dataSource.filter = JSON.stringify(res);
});
}
我们的“customFilter”可以像
customFilter = (data: any, filter: string) => {
const filterData = JSON.parse(filter);
let ok = true;
if (filterData._general)
{
const search = filterData._general.toLowerCase();
ok=false;
for (const prop in data) {
ok = ok || (''+data[prop]).toLowerCase().indexOf(search) >= 0;
}
}
Object.keys(filterData).forEach((x) => {
if (x!='_general' && filterData[x]) {
if (ok) ok = (''+data[x]).toLowerCase().indexOf(filterData[x].toLowerCase())>=0;
}
});
return ok;
};
最后,将table封装在一个formGroup中。
<form *ngIf="form" [formGroup]="form">
<mat-form-field *ngIf="filter">
<input
matInput formControlName="_general"
placeholder="{{ filterPlaceholder }}"
/>
</mat-form-field>
<div class="mat-elevation-z8">
<table ...>
...
<!-- our "inputs" filter becomes like-->
<mat-form-field *ngIf="i >= 0" appearance="outline">
<input matInput
placeholder="Press 'Enter' to search"
[formControlName]="column.columnDef"
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
...
</table>
</form>
更新我的错!!
由于我们将 table 置于 *ngIf="form"
下,因此“分页器”(和排序)在创建表单之前不可访问。为此我们需要做一些改变
我们删除了 ViewChild(MatPaginator)
中的 {static:true}
和
ViewChild(MatSort)
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
请记住,如果元素是,我们只能使用 {static:true}
总是在组件中 - 如果它不在 *ngIf-
在组件AfterViewInit中实现
export class DataTableDynamicComponent implements OnChanges,AfterViewInit{..}
在这个函数中我们需要分页器和排序
ngAfterViewInit()
{
this.dataSource.paginator=this.paginator
this.dataSource.sort=this.sort
}
各位。
我需要帮助。 我正在创建一个必须包含全局筛选器和列筛选器的数据透视表 table。 但是我编码的方式我不明白。
遵循 stackblitz
的代码和 link<p>data-table-dynamic works!</p>
<mat-form-field *ngIf="filter">
<input
matInput
(keyup)="applyFilter($event)"
placeholder="{{ filterPlaceholder }}"
/>
</mat-form-field>
<div class="mat-elevation-z8">
<table
mat-table
[dataSource]="dataSource"
matSort
[ngStyle]="{ 'min-width': +tableMinWidth + 'px' }"
>
<ng-container *ngFor="let column of columns">
<ng-container matColumnDef="{{ column.columnDef }}">
<th class="header" mat-header-cell *matHeaderCellDef mat-sort-header>
<div fxFlexFill>
{{ column.header }}
</div>
</th>
<td mat-cell *matCellDef="let row">{{ column.cell(row) }}</td>
</ng-container>
</ng-container>
<ng-container *ngIf="buttons.length >= 0">
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>
<button
*ngIf="columnsFilter"
mat-icon-button
matTooltip="Toggle Filters"
(click)="toggleFilters = !toggleFilters"
>
<mat-icon>search</mat-icon>
</button>
</th>
<td
mat-cell
*matCellDef="let row"
[ngStyle]="{ 'min-width': 'calc(55px * ' + buttons.length + ')' }"
>
<div class="btn-group" *ngFor="let button of buttons">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
[matMenuTriggerData]="{ data: row }"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</td>
</ng-container>
</ng-container>
<ng-container *ngFor="let column of columns; let i = index">
<ng-container matColumnDef="{{ column.columnSearch }}">
<th class="header" mat-header-cell *matHeaderCellDef>
<div
fxFlexFill
class="filters-container"
[class.animate]="toggleFilters"
>
<mat-form-field *ngIf="i >= 0" appearance="outline">
<input
matInput
placeholder="Press 'Enter' to search"
[(ngModel)]="filtersModel[i]"
(keyup)="searchColumns()"
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</div>
</th>
<td mat-cell *matCellDef="let row">{{ column.cell(row) }}</td>
</ng-container>
</ng-container>
<ng-container matColumnDef="filter" *ngIf="columnsFilter">
<th mat-header-cell *matHeaderCellDef class="filterHeaderCell">
<div class="filters-container" [class.animate]="toggleFilters">
<button
mat-icon-button
matTooltip="Clear Filters"
(click)="clearFilters()"
>
<mat-icon>search_off</mat-icon>
</button>
</div>
</th>
</ng-container>
<!-- Disclaimer column - with nullable approach -->
<ng-container matColumnDef="disclaimer" *ngIf="footer">
<td mat-footer-cell *matFooterCellDef colspan="100%">
<strong>{{ footer }}</strong>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<ng-container *ngIf="columnsFilter">
<tr
mat-header-row
*matHeaderRowDef="displayedColumnsSearch"
class="mat-header-filter"
></tr>
<!-- class="no-default-height" -->
</ng-container>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<!-- <tr mat-header-row *matHeaderRowDef="searchColumn"></tr> -->
<ng-container *ngIf="footer">
<!-- Make footer nullable -->
<tr
mat-footer-row
*matFooterRowDef="['disclaimer']"
class="second-footer-row"
></tr>
</ng-container>
</table>
<mat-paginator
[pageSizeOptions]="pagination"
[pageSize]="pageSize"
[ngStyle]="{ 'min-width': +tableMinWidth + 'px' }"
></mat-paginator>
<mat-menu #menu="matMenu">
<ng-template matMenuContent let-data="data">
<div *ngFor="let button of menuButtons">
<button
mat-menu-item
(click)="this.buttonClick.emit([button.action, button.payload(data)])"
>
<mat-icon>{{ button.icon }}</mat-icon>
{{ button.description }}
</button>
</div>
</ng-template>
</mat-menu>
</div>
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { TableBtn, TableColumn } from '../../core/interfaces';
import { TableMenu } from '../../core/interfaces/table-menu';
@Component({
selector: 'app-data-table-dynamic',
templateUrl: './data-table-dynamic.component.html',
styleUrls: ['./data-table-dynamic.component.scss'],
})
export class DataTableDynamicComponent implements OnChanges, OnInit {
@Input() columns: TableColumn[] = [];
@Input() buttons: TableBtn[] = [];
@Input() menuButtons: TableMenu[] = [];
@Input() data: any[] = [];
@Input() filter: boolean = false;
@Input() filterPlaceholder: string = 'Filter';
@Input() columnsFilter: boolean = false;
@Input() footer: string = null;
@Input() pagination: number[] = [];
@Input() pageSize: number;
@Input() tableMinWidth: number = 500;
@Output() filteredData = new EventEmitter<any[]>();
@Output() buttonClick = new EventEmitter<string[]>();
dataSource: MatTableDataSource<any>;
displayedColumns: string[];
displayedColumnsSearch: string[];
headers: string[] = this.columns.map((x) => x.columnDef);
headersFilters = this.headers.map((x, i) => x + '_' + i);
filtersModel = [];
filterKeys = {};
toggleFilters = true;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;
constructor() {}
ngOnInit(): void {}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
if (this.data) {
if (changes.data) {
this.dataSource = new MatTableDataSource(this.data);
this.dataSource.filterPredicate = (item, filter: string) => {
const colMatch = !Object.keys(this.filterKeys).reduce(
(remove, field) => {
return (
remove ||
!item[field]
.toString()
.toLocaleLowerCase()
.includes(this.filterKeys[field])
);
},
false
);
return colMatch;
};
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
this.displayedColumns = [...this.columns.map((c) => c.columnDef)];
this.displayedColumnsSearch = [
...this.columns.map((c) => c.columnSearch),
'filter',
];
this.columns.forEach((value, index) => {
this.filterKeys[this.columns[index].columnDef] = '';
});
if (this.buttons.length > 0)
this.displayedColumns = [...this.displayedColumns, 'actions'];
}
}
}
applyFilter(filterValue) {
this.dataSource.filter = filterValue.target.value.trim().toLowerCase();
this.filteredData.emit(this.dataSource.filteredData);
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
this.dataSource.sort = this.sort;
}
searchColumns() {
this.filtersModel.forEach((each, ind) => {
this.filterKeys[this.columns[ind].columnDef] = each || '';
});
//Call API with filters
this.dataSource.filter = JSON.stringify(this.filterKeys);
this.filteredData.emit(this.dataSource.filteredData);
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
this.dataSource.sort = this.sort;
}
clearFilters() {
this.filtersModel = [];
this.columns.forEach((value, index) => {
this.filterKeys[this.columns[index].columnDef] = '';
});
//Call API without filters
this.searchColumns();
}
}
我知道不是这样,但我做了一些测试,但我做不到。我把工作代码,所以你可以帮助我。 如果您有任何文章或示例,将会有很大帮助。
我以这段代码为例,并根据需要进行了调整。
好吧,我们可以使用列的值创建一个 FormGroup,为此 columns' is a property and we use the way
@Input('columns') set _(value)`
form: FormGroup = new FormGroup({});
columns: TableColumn[] = [];
@Input('columns') set _columns(value) {
this.columns = value;
value.forEach((x) => {
this.form.addControl(x.columnDef, new FormControl());
});
this.form.addControl('_general', new FormControl());
this.form.valueChanges.subscribe((res) => {
this.dataSource.filter = JSON.stringify(res);
});
}
我们的“customFilter”可以像
customFilter = (data: any, filter: string) => {
const filterData = JSON.parse(filter);
let ok = true;
if (filterData._general)
{
const search = filterData._general.toLowerCase();
ok=false;
for (const prop in data) {
ok = ok || (''+data[prop]).toLowerCase().indexOf(search) >= 0;
}
}
Object.keys(filterData).forEach((x) => {
if (x!='_general' && filterData[x]) {
if (ok) ok = (''+data[x]).toLowerCase().indexOf(filterData[x].toLowerCase())>=0;
}
});
return ok;
};
最后,将table封装在一个formGroup中。
<form *ngIf="form" [formGroup]="form">
<mat-form-field *ngIf="filter">
<input
matInput formControlName="_general"
placeholder="{{ filterPlaceholder }}"
/>
</mat-form-field>
<div class="mat-elevation-z8">
<table ...>
...
<!-- our "inputs" filter becomes like-->
<mat-form-field *ngIf="i >= 0" appearance="outline">
<input matInput
placeholder="Press 'Enter' to search"
[formControlName]="column.columnDef"
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
...
</table>
</form>
更新我的错!!
由于我们将 table 置于 *ngIf="form"
下,因此“分页器”(和排序)在创建表单之前不可访问。为此我们需要做一些改变
我们删除了
ViewChild(MatPaginator)
中的{static:true}
和ViewChild(MatSort)
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort;
请记住,如果元素是,我们只能使用
{static:true}
总是在组件中 - 如果它不在 *ngIf-在组件AfterViewInit中实现
export class DataTableDynamicComponent implements OnChanges,AfterViewInit{..}
在这个函数中我们需要分页器和排序
ngAfterViewInit() { this.dataSource.paginator=this.paginator this.dataSource.sort=this.sort }