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();
  }
}

StackBlitz

我知道不是这样,但我做了一些测试,但我做不到。我把工作代码,所以你可以帮助我。 如果您有任何文章或示例,将会有很大帮助。

我以这段代码为例,并根据需要进行了调整。

好吧,我们可以使用列的值创建一个 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" 下,因此“分页器”(和排序)在创建表单之前不可访问。为此我们需要做一些改变

  1. 我们删除了 ViewChild(MatPaginator) 中的 {static:true}ViewChild(MatSort)

      @ViewChild(MatPaginator) paginator: MatPaginator;
      @ViewChild(MatSort) sort: MatSort;
    

    请记住,如果元素是,我们只能使用 {static:true} 总是在组件中 - 如果它不在 *ngIf-

  2. 在组件AfterViewInit中实现

    export class DataTableDynamicComponent implements OnChanges,AfterViewInit{..}
    
  3. 在这个函数中我们需要分页器和排序

      ngAfterViewInit()
      {
        this.dataSource.paginator=this.paginator
        this.dataSource.sort=this.sort
    
      }