15

I am trying to add different filters to a material table. To be more precise I am trying to reproduce something similar to the following GIF

image description

For doing so I am following irowbin's response in the following Github thread but I can't really produce what I want based on his guidelines

Is there any working example on Stackblitz or Github so I can follow and create multiple search filters inside mattable?

Muhammed Albarmavi
  • 23,240
  • 8
  • 66
  • 91
MHOOS
  • 5,146
  • 11
  • 39
  • 74

2 Answers2

29

this an example of implement column filter for angular material table based on other material components

structure of column filter for single column

<ng-container matColumnDef="position">
  <th mat-header-cell *matHeaderCellDef>
    <div class="header">
      No.
      <button mat-button class="btn-toggle" [matMenuTriggerFor]="menu">
        <mat-icon>keyboard_arrow_down</mat-icon>
      </button>

    </div>
    <mat-menu #menu>
      <div mat-menu-item mat-filter-item [disableRipple]="true" class="menu-title">
        No.
      </div>
      <div mat-menu-item mat-filter-item [disableRipple]="true">
        <mat-form-field>
          <mat-select [panelClass]="'mat-elevation-z10'" placeholder='Conditions' [(value)]="searchCondition.position">
            <mat-option *ngFor="let  condition of conditionsList" [value]="condition.value">{{condition.label}}</mat-option>
          </mat-select>
        </mat-form-field>
      </div>

      <div mat-menu-item mat-filter-item [disableRipple]="true">
        <mat-form-field>
          <input matInput placeholder="Value" [(ngModel)]="searchValue.position">
        </mat-form-field>
      </div>

      <div mat-menu-item mat-filter-item [disableRipple]="true">
        <button mat-raised-button (click)="clearColumn('position')">Clear</button>
        <button mat-raised-button color="primary" (click)="applyFilter()">Search</button>

      </div>
    </mat-menu>

  </th>
  <td mat-cell *matCellDef="let element"> {{element.position}} </td>
</ng-container>

mat-filter-item directive use to prevent hide the mat-menu on click event

conditions list and functions

export const CONDITIONS_LIST = [
  { value: "nono", label: "Nono" },
  { value: "is-empty", label: "Is empty" },
  { value: "is-not-empty", label: "Is not empty" },
  { value: "is-equal", label: "Is equal" },
  { value: "is-not-equal", label: "Is not equal" }
];

export const CONDITIONS_FUNCTIONS = { // search method base on conditions list value
  "is-empty": function (value, filterdValue) {
    return value === "";
  },
  "is-not-empty": function (value, filterdValue) {
    return value !== "";
  },
  "is-equal": function (value, filterdValue) {
    return value == filterdValue;
  },
  "is-not-equal": function (value, filterdValue) {
    return value != filterdValue;
  }
};

component

 public displayedColumns: string[] = ["position", "name", "weight", "symbol"];
  public dataSource = new MatTableDataSource(ELEMENT_DATA);

  public conditionsList = CONDITIONS_LIST;
  public searchValue: any = {};
  public searchCondition: any = {};
  private _filterMethods = CONDITIONS_FUNCTIONS;

  constructor() { }

  ngOnInit() {
    this.dataSource.filterPredicate = (p: PeriodicElement, filtre: any) => {
      let result = true;
      let keys = Object.keys(p); // keys of the object data 

      for (const key of keys) {
        let searchCondition = filtre.conditions[key]; // get search filter method

        if (searchCondition && searchCondition !== 'none') {
          if (filtre.methods[searchCondition](p[key], filtre.values[key]) === false) { // invoke search filter 
            result = false // if one of the filters method not succeed the row will be remove from the filter result 
            break;
          }
        }
      }

      return result
    };
  }

  applyFilter() {

    let searchFilter: any = {
      values: this.searchValue,
      conditions: this.searchCondition,
      methods: this._filterMethods
    }

    this.dataSource.filter = searchFilter;
  }

  clearColumn(columnKey: string): void {
    this.searchValue[columnKey] = null;
    this.searchCondition[columnKey] = 'none';
    this.applyFilter();
  }

source / preview

Muhammed Albarmavi
  • 23,240
  • 8
  • 66
  • 91
  • is the above is sufficient for making it worked? I followed above code but its breaking for me. Can you help me what else is needed to make the above filter functionality workable in my project? Thanks. – Nag Arjun Feb 13 '21 at 00:38
  • 1
    Preview is actually here: https://malbarmavi.github.io/mat-table-column-filter/ – jstell Oct 21 '21 at 16:11
0

You can use mat-table-filter for filtering. It is inspired by hibernate's example api. You can achieve column filtering with minimal effort. It saves you from implementing filtering boilerplate including debounce etc. It supports array filtering too.

The only thing you need to do is adding matTableFilter directive to your material table and binding exampleEntity with a representation of what you have as an item inside your datasource.

 <table mat-table matTableFilter [dataSource]="dataSource"
 [exampleEntity]="exampleObject"...>

That's all. When you populate the exampleObject's properties, the filter will automatically work just fine with the default debounce support. You can change the debounce time also.

You can take a look at the examples here: https://halittalha.github.io/ng-material-extensions/

I'm sharing the full array filtering source code below. The below example leverages chips component to collect the array contents for filtering.

.html

<mat-table matTableFilter [exampleEntity]="filterEntity" [filterType]="filterType" [dataSource]="dataSource"
  class="mat-elevation-z8">
  <ng-container matColumnDef="category">
    <mat-header-cell *matHeaderCellDef>
      <mat-form-field appearance="outline">
        <input matInput placeholder="Category" [(ngModel)]="filterEntity.category">
      </mat-form-field>
    </mat-header-cell>
    <mat-cell *matCellDef="let element"> {{element.category}} </mat-cell>
  </ng-container>

  <ng-container matColumnDef="brand">
    <mat-header-cell *matHeaderCellDef>
      <mat-form-field appearance="outline">
        <input matInput placeholder="Product Brand" [(ngModel)]="filterEntity.brand">
      </mat-form-field>
    </mat-header-cell>
    <mat-cell *matCellDef="let element"> {{element.brand}} </mat-cell>
  </ng-container>

  <ng-container matColumnDef="availableSizes">
    <mat-header-cell *matHeaderCellDef>

      <mat-form-field class="example-chip-list">
        <mat-chip-list #chipList aria-label="Fruit selection">
          <mat-chip *ngFor="let size of filterEntity.availableSizes" [selectable]="true" [removable]="true"
            (removed)="remove(size)">
            {{size}}
            <mat-icon matChipRemove>cancel</mat-icon>
          </mat-chip>
          <input placeholder="Add Size" [matChipInputFor]="chipList"
            [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
            (matChipInputTokenEnd)="add($event)">
        </mat-chip-list>
      </mat-form-field>
    </mat-header-cell>
    <mat-cell *matCellDef="let element"> {{element.availableSizes}} </mat-cell>
  </ng-container>

  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>

.ts

import { MatTableFilter } from 'mat-table-filter';
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource, MatChipInputEvent } from '@angular/material';
import { COMMA, ENTER } from '@angular/cdk/keycodes';

export class Product {
  category: string;
  brand: string;
  availableSizes: Array<string>;
}

const PRODUCTS: Product[] = [
  {category: 'T-Shirt', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'T-Shirt', brand: 'Y', availableSizes: ['S', 'L', 'XL']},
  {category: 'T-Shirt', brand: 'Z', availableSizes: ['XL']},
  {category: 'Jean', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'Jean', brand: 'Y', availableSizes: ['S', 'M']},
  {category: 'Jean', brand: 'Z', availableSizes: ['S', 'M', 'L']},
  {category: 'Jean', brand: 'B', availableSizes: ['S', 'M', 'L']},
  {category: 'Jacket', brand: 'X', availableSizes: ['S', 'L', 'XL']},
  {category: 'Jacket', brand: 'Z', availableSizes: ['S']},
  {category: 'Pants', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'Pants', brand: 'Y', availableSizes: ['L', 'XL']},
  {category: 'Pants', brand: 'Z', availableSizes: ['S']},
  {category: 'Hat', brand: 'X', availableSizes: ['S', 'M', 'L']},
  {category: 'Skirt', brand: 'X', availableSizes: ['S', 'M', 'L', 'XL']},
  {category: 'Skirt', brand: 'Y', availableSizes: ['S', 'M', 'L']}
 ];

@Component({
  selector: 'app-array-filter',
  templateUrl: './array-filter.component.html',
  styleUrls: ['./array-filter.component.css']
})
export class ArrayFilterComponent implements OnInit {
  filterEntity: Product;
  filterType: MatTableFilter;
  displayedColumns: string[] = ['category', 'brand', 'availableSizes'];
  dataSource;
  readonly separatorKeysCodes: number[] = [ENTER, COMMA];

  add(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;

    if ((value || '').trim()) {
      this.filterEntity.availableSizes.push(value.trim());
    }

    // Reset the input value
    if (input) {
      input.value = '';
    }
  }

  remove(size: string): void {
    const index = this.filterEntity.availableSizes.indexOf(size);

    if (index >= 0) {
      this.filterEntity.availableSizes.splice(index, 1);
    }
  }

  ngOnInit() {
    this.filterEntity = new Product();
    this.filterEntity.availableSizes = new Array<string>(); // DO NOT FORGET TO INIT THE ARRAY
    this.filterType = MatTableFilter.ANYWHERE;
    this.dataSource = new MatTableDataSource(PRODUCTS);
  }
}
talhature
  • 2,246
  • 1
  • 14
  • 28