Filtering material table data sources based on column values

Is there a way to implement a filter on the material data table for a specific column?


public dataSource;

this.dataSource = new MatTableDataSource(this.items);
this.dataSource.filterPredicate = function customFilter(data , filter:string ): boolean {
    return (data.name.startsWith(filter));
}

applyFilter(filterValue: string) {
    filterValue = filterValue.trim(); // Remove whitespace
    filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
    this.dataSource.filter = filterValue;
}

Unfortunately, the code snippet above does not seem to be functioning properly. When I try to use it, no data is being matched.

Answer №1

According to information found in the documentation.

Suppose you have a data object like {id: 123, name: 'Mr. Smith', favoriteColor: 'blue'}. It will be condensed to 123mr. smithblue. If your filter string was blue, it would count as a match because it is within the condensed string, and the corresponding row would show up in the table.

To change the default filtering behavior, you can define a custom filterPredicate function that takes a data object and a filter string, and returns true if the data object should be considered a match.

If you wish to filter only specific columns, you need to adjust the filterPredicate. There is already a solution provided here.

This example demonstrates how filtering works:

table-filtering-example.html

<div class="example-container mat-elevation-z8">
  <div class="example-header">
    <mat-form-field>
      <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
    </mat-form-field>
  </div>

  <mat-table #table [dataSource]="dataSource">

    <!-- Position Column -->
    <ng-container matColumnDef="position">
      <mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.position}} </mat-cell>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
    </ng-container>

    <!-- Weight Column -->
    <ng-container matColumnDef="weight">
      <mat-header-cell *matHeaderCellDef> Weight </mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell>
    </ng-container>

    <!-- Symbol Column -->
    <ng-container matColumnDef="symbol">
      <mat-header-cell *matHeaderCellDef> Symbol </mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell>
    </ng-container>

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

table-filtering-example.ts

  import {Component} from '@angular/core';
    import {MatTableDataSource} from '@angular/material';
    
    /**
     * @title Table with filtering
     */
    @Component({
      selector: 'table-filtering-example',
      styleUrls: ['table-filtering-example.css'],
      templateUrl: 'table-filtering-example.html',
    })
    export class TableFilteringExample {
      displayedColumns = ['position', 'name', 'weight', 'symbol'];
      dataSource = new MatTableDataSource(ELEMENT_DATA);
    
      applyFilter(filterValue: string) {
        filterValue = filterValue.trim(); // Remove whitespace
        filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
        this.dataSource.filter = filterValue;
      }
    }
    
    export interface Element {
      name: string;
      position: number;
      weight: number;
      symbol: string;
    }
    
    const ELEMENT_DATA: Element[] = [
      {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
      {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
      {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
      {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
      {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
      {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
      {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
      {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
      {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
      {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
      {position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na'},
      {position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg'},
      {position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al'},
      {position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si'},
      {position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P'},
      {position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S'},
      {position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl'},
      {position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar'},
      {position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K'},
      {position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca'}, ];

You can utilize filterPredicate to filter a specific column as demonstrated below:

  ngOnInit() {
    this.dataSource.filterPredicate = (data: Element, filter: string) => {
      return data.name == filter;
     };
   }
 applyFilter(filterValue: string) {
    // filterValue = filterValue.trim(); // Remove whitespace
    // filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
    this.dataSource.filter = filterValue;
  }

I've revised the applyFilter() method and included ngOnInit(). Now, it filters only the name column and checks for an exact match (==).

Answer №2

One way to implement dynamic column filtering without hardcoding the column names is as follows:

// Set up filterPredicate to filter by input column only when focused
setupFilter(column: string) {
  this.dataSource.filterPredicate = (d: TableDataSourceType, filter: string) => {
    const textToSearch = d[column] && d[column].toLowerCase() || '';
    return textToSearch.indexOf(filter) !== -1;
  };
}

applyFilter(filterValue: string) {
  this.dataSource.filter = filterValue.trim().toLowerCase();
}

In your template, you can include the following:

<ng-container matColumnDef="item-filter">
  <th mat-header-cell *matHeaderCellDef>
    <input (keyup)="applyFilter($event.target.value)" (focus)="setupFilter('name')" />
  </th>
</ng-container>

Another approach is to dynamically create a header row with per-column filtering:

<table mat-table [dataSource]="dataSource">
   <ng-container *ngFor="let filterCol of ['names', 'age', 'address']">
     <ng-container matColumnDef="filterCol">
       <th mat-header-cell *matHeaderCellDef>
         <input (keyup)="applyFilter($event.target.value)" (focus)="setupFilter(filterCol)"/>
       </th>
     </ng-container>
   </ng-container>

   <tr mat-header-row *matHeaderRowDef="['names', 'age', 'address']"></tr>
</table>

Note that having multiple header rows with the same keys is not supported:

<tr mat-header-row *matHeaderRowDef="['names', 'age', 'address']"></tr>
<tr mat-header-row *matHeaderRowDef="['names', 'age', 'address']"></tr>

Answer №3

Create a customized filter select box for the Material table by utilizing the filterPredicate and overriding it with the customFilter() method.

Check out the demo Here

Find the source Here

        ...
        ngOnInit() {
            this.getRemoteData();

            // Customize default filter behavior of Material Datatable
            this.dataSource.filterPredicate = this.createCustomFilter();
        }
        ...

        // Customized filter method for Angular Material Datatable
        createCustomFilter() {
            let filterFunction = function (data: any, filter: string): boolean {
            let searchTerms = JSON.parse(filter);
            let isFilterSet = false;
            for (const col in searchTerms) {
                if (searchTerms[col].toString() !== '') {
                isFilterSet = true;
                } else {
                delete searchTerms[col];
                }
            }

            let nameSearch = () => {
                let found = false;
                if (isFilterSet) {
                for (const col in searchTerms) {
                    searchTerms[col].trim().toLowerCase().split(' ').forEach(word => {
                    if (data[col].toString().toLowerCase().indexOf(word) != -1 && isFilterSet) {
                        found = true
                    }
                    });
                }
                return found
                } else {
                return true;
                }
            }
            return nameSearch()
            }
            return filterFunction
        }

Answer №4

custom.component.html

<mat-form-field floatPlaceholder="never">
            <input matInput placeholder="Filter name" (keyup)="applyFilter($event.target.value)">
          </mat-form-field>
        <mat-table matSort  [dataSource]="dataSource" class="mat-elevation-z8">
           
            <!-- Position Column -->
            <ng-container matColumnDef="position">
              <mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell>
              <mat-cell *matCellDef="let element"> {{element.position}} </mat-cell>
            </ng-container>
          
            <!-- Name Column -->
            <ng-container matColumnDef="name">
              <mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
              <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
            </ng-container>
          
            <!-- Weight Column -->
            <ng-container matColumnDef="weight">
              <mat-header-cell *matHeaderCellDef mat-sort-header> Weight </mat-header-cell>
              <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell>
            </ng-container>
          
            <!-- Symbol Column -->
            <ng-container matColumnDef="symbol">
              <mat-header-cell *matHeaderCellDef mat-sort-header> Symbol </mat-header-cell>
              <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell>
            </ng-container>
          
            <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
            <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
          </mat-table>
          <mat-paginator [pageSize]="5" [pageSizeOptions]="[5, 10, 15]" showFirstLastButtons></mat-paginator>

custom.component.ts

import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
  { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
  { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
  { position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
  { position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
  { position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
  { position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
  { position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
  { position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
  { position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
];

@Component({
  selector: 'app-custom',
  templateUrl: './custom.component.html',
  styleUrls: ['./custom.component.css'],
})
export class CustomComponent {
  displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
  dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);

  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
  @ViewChild(MatSort, { static: true }) sort: MatSort;

  ngOnInit() {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }
  applyFilter(filterValue: string) {
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }
}

Answer №5

applySearchFilter(event: Event) {
    const inputValue = (event.target as HTMLInputElement).value;
    if (inputValue == '') {
        this.filteredData = this.dataSource;
    }
    else if (inputValue != '') {
        this.filteredData = this.dataSource.filter(item =>
            item.name.toLowerCase().includes(inputValue.trim().toLowerCase()) ||
            item.description.toLowerCase().includes(inputValue.trim().toLowerCase()) ||
            item.status.toString().toLowerCase().includes(inputValue.trim().toLowerCase()) ||
            item.dateCreated.toLowerCase().includes(inputValue.trim().toLowerCase())
        );
    }
}

Answer №6

Check out this unique approach - implementing an inline filter with mat-input, mat-icon, and multiple header rows in Angular Material table.

https://i.sstatic.net/KPkqW.png

Answer №7

Filtering data is essential in any application. To achieve this, I utilized the FilterPredicate function which checks if a row exists and returns true. A formGroup was created with matching column names to facilitate filtering. The filterPreticate function was then implemented to filter the data based on specified criteria.

this.dataSource.filterPredicate = (
    data: { [name: string]: unknown },
    filter: { [name: string]: unknown }
  ): boolean => {
    for (const key in filter) {
      if (key in data) {
        if (
          data[key].toString().trim().toLowerCase() !==
          filter[key].toString().trim().toLowerCase()
        ) {
          return false;
        }
      }
    }
    return true;
  };

In addition, before passing the value, any undefined, null, or empty key values were removed from the formValue Object.

applyFilter() {
 const cleanObject: { [name: string]: unknown } = {};
  for (const [key, value] of Object.entries(
    this.filterForm.value as { [name: string]: unknown }
  )) {
    if (value !== undefined && value !== null && value !== '') {
      cleanObject[key] = value;
    }
  }
  this.dataSource.filter = cleanObject;
}

If there is a need to filter columns based on substring matches, the following code snippet can be used:

this.dataSource.filterPredicate = (
    data: { [name: string]: unknown },
    filter: { [name: string]: unknown }
  ): boolean => {
    for (const key in filter) {
      if (key in data && typeof data[key] === 'string') {
        const dataValue = data[key].toString().trim().toLowerCase();
        const filterValue = filter[key].toString().trim().toLowerCase();
        if (!(dataValue.indexOf(filterValue) !== -1)) {
          return false;
        }
      }
    }
    return true;
  };

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the method for displaying an object as JSON on the console in Angular2?

I've been utilizing a service to input my form data into an array within my angular2 application. The information is organized in the following manner: arr = [] arr.push({title:name}) After executing console.log(arr), it displays as Object. However, ...

[Heroku][Angular] Dealing with "Template parse errors" upon deploying my application

Encountered errors while deploying my application using "express" on Heroku: Uncaught Error: Template parse errors: Unexpected closing tag "button". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR ...

What is the best way to choose distinct items from a Promise<Array[]> in Angular 2/4?

Providing some context, I am working with an API that fetches information about a competition including links to matches, and each match has links to the teams involved. My current goal is to create a standings table based on the matches of the competitio ...

The Angular service is unable to access the Slim REST API endpoint

I have successfully configured a Slim REST API to retrieve data from the table trading_partner. <?php // Establish database connection require_once('dbconnect.php'); // Fetch all records $app->get('/path/to/trading_partner', fun ...

Tips for minimizing the amount of code in Angular 5

As our Angular 5 application continues to expand, due to being a Single Page Application with dynamic components, our main component is becoming overloaded. The main component is responsible for rendering other components and contains the majority of the l ...

Rising Memory Usage Seen in Usage of Typescript and Googleapis

After observing an intermittent Out of Memory error during the build step for some time, we finally identified the exact commit that triggered it. This particular commit caused the RAM usage for the tsc --alwaysStrict build process to escalate significantl ...

Is there a way to transfer the information between component 1 and component 3 using a single service?

Check out my codes on this link. Within my codebase, there is a TransferObjService object, along with components named "aa" and "bb". The TransferObjService object is utilized to send the string "Angular" to the aa component. Within the aa component, I ...

Retrieving a distinct value from an Observable

I am currently attempting to extract the monthlyFee value from this specific response body. ...

What is the best way to incorporate various icons into a dynamic table based on the incoming data?

I am working with an Angular Material dynamic table and I want to customize the colors or icons based on the data received. I don't want every row to have the same color or icon, but rather apply it selectively. Although each row currently has a butto ...

Comparing dates for equality in Sequelize - a comprehensive guide

Can someone help me with comparing equality between two dates in my code? I have attempted the following but it does not seem to work: const result: SomeModel= SomeModel.findOne( {where: { startTime : { [ ...

A guide on verifying http calls using sinon in an Angular project with Typescript

Our team is in the process of transitioning to TypeScript, but we want to continue using Sinon for our tests. In the past, we used JavaScript Service Unit Tests like this: it('should get buyer application status count', function () { le ...

Developing applications using Angular/Ionic framework that follows a component-based architecture

I currently have 2 child components displayed below. Child component 1: https://i.sstatic.net/JKzGy.png Child component 2: https://i.sstatic.net/u0YpX.png The styles, positions, and other aspects are identical in both components. However, the content ...

Locate and embed within a sophisticated JSON structure

I have an object structured as follows: interface Employee { id: number; name: string; parentid: number; level: string; children?: Employee[]; } const Data: Employee[] = [ { id:1, name: 'name1', parentid:0, level: 'L1', children: [ ...

Using a typescript interface field as a reference

As I work on a react component, I am interested in passing a reference to a field within an interface. When working with interfaces, such as the example below: interface Person { name: String } I want to pass a ref specifically for accessing Person::name. ...

"Difficulties with integrating Three JS as a scrolling component in Angular

Currently, I am working on integrating a threejs scene as an Angular component for my application. Initially, everything was going smoothly as per the tutorials I found. However, I faced an issue when I added OrbitControls to enable zooming within the scen ...

Dynamic TypeScript property that can only be assigned values from an array during runtime

I'm currently struggling with specifying allowed values for a property in TypeScript. Within my interface, I have statically typed the property like this: interface SomeInterface{ prop: "bell" | "edit" | "log-out" } However, I am looking for a w ...

What is the best way to initiate a class constructor with certain parameters left out?

Currently, I am facing a challenge while trying to pass various combinations of arguments to a class constructor. The constructor has 2 optional arguments. Here is the code snippet: class MyClass { foo(a, b) { return new MyClass(a, b); } bar( ...

What measures can be taken to stop an event from being triggered from an external source?

Consider a scenario where there is a simple counting process functioning as a default Node EventEmitter: import {EventEmitter} from 'events'; async function sleep(milliseconds: number): Promise<void> { return new Promise((resolve) => ...

Ways to effectively utilize an interface incorporating props.children and other property varieties

Currently working on a project with Next.js and Typescript. In order to create a layout component, I defined the following interface: export interface AuxProps { children: React.ReactNode; pageTitle: 'string'; } The layout component code sn ...

Locating and casting array elements correctly with union types and generics: a guide

My type declarations are as follows: types.ts: type ItemKind = 'A' | 'B'; type ValidItem<TItemKind extends ItemKind = ItemKind> = { readonly type: TItemKind; readonly id: number; }; type EmptyItem<TItemKind extends ...