Filter multiple columns in an Angular custom table with a unique filterPredicate

Looking to develop a versatile table that accepts tableColumns and dataSource as @Input(). I want the ability to add custom filtering for each table column. Currently, I've set up the initialization of the table FormGroup and retrieving its value for filtering. However, beyond this point, I'm unsure of how to proceed. I've experimented with implementing pipes and exploring similar solutions online, but they all require knowledge of my tableColumns (which are dynamically created).

Below is a snippet of my code:

    
<table mat-table [dataSource]="dataSource">
  <ng-container *ngFor="let col of tableColumns; let i = index" [matColumnDef]="col.key">
    <ng-container>
      <th class="header" mat-header-cell *matHeaderCellDef>
        <span *ngIf="!col.config">
          {{ col['display'] }}
        </span>
        <form [formGroup]="form">
          <mat-form-field *ngIf="!col.config" class="filter">
            <input matInput placeholder="Filter" formControlName="{{tableColumns[i].key}}">
          </mat-form-field>
        </form>
      </th>
      <td mat-cell *matCellDef="let element; let row"> 
        <ng-container>
          {{ element[col.key] }}
        </ng-container>   
      </td>
    </ng-container>
  </ng-container>
  <tr mat-header-row *matHeaderRowDef="keys"></tr>
  <tr mat-row *matRowDef="let row; columns: keys"></tr>
</table>

My TypeScript file includes:

  
@Input() tableColumns: GenericTableColumn[] = []
@Input() dataSource: Array<object> = []

ngOnInit(): void {
this.initFormControls()
this.registerFormChangeListener()
}

initFormControls() {
const formGroup: FormGroup = new FormGroup({})
this.tableColumns.forEach((column) => {
if (column.key !== 'actions') {
let control: FormControl = new FormControl('')
formGroup.addControl(column.key, control)
}
})
this.form = formGroup
}

The plan was to create a function to convert the dataSource input into a MatTableDataSource and apply a custom filter predicate. Here's a rough idea:

  
registerFormChangeListener(){
const tableDataSource = new MatTableDataSource(this.dataSource)
tableDataSource.filterPredicate((tableDataSource, filter) => {
// need to filter by object key value here
})
}

Sample dataSource:


{
{
"id": "1",
"name": "someValue1",
"someOtherKey": "someOtherValue"
},
{
"id": "2",
"name": "someValue2",
"someOtherKey": "someOtherValue2"
},
{
"id": "3",
"name": "someValue3",
"someOtherKey": "someOtherValue2"
}
}

Expected filter object:


{
"id": "",
"name": "someValue1",
"someOtherKey": "someOtherValue2"
}

Hoping to achieve filtered results like:


const fitleredResults = [
{
"id": "1",
"name": "someValue1",
"someOtherKey": "someOtherValue"
},
{
"id": "2",
"name": "someValue2",
"someOtherKey": "someOtherValue2"
}
]

Appreciate any guidance!

Answer №1

In order to understand how a customFilter function operates, it is essential to grasp the underlying mechanics.

  1. Within a MatTableDataSource object, there exists a property named filter which holds a string value.
  2. A customFilter function is designed to receive each element of an array as an argument, treating it as a "string", and then returning either true or false based on specified conditions.

When dealing with multiple fields for filtering purposes, utilizing JSON.stringify can aid in creating the necessary "string", while using JSON.parse helps in recovering the original object.

You may find inspiration from this post on Stack Overflow.

Tip: When iterating over all keys within an object, consider using Object.Keys or Object.Entries.

Update

initFormControls() {
    const formGroup: FormGroup = new FormGroup({})
    ...
    this.form = formGroup
    //we subscribe to valueChanges
    this.form.valueChanges.subscribe(res => {
        this.dataSource.filter = JSON.stringify(res);
    });
 
  }

  customFilter = (data: any, filter: string) => {
    const filterData = JSON.parse(filter);
    return Object.keys(filterData).reduce((a: boolean, key: string) => {
      if (!a) return false;
      if (filterData[key])
        return ('' + data[key]).indexOf('' + filterData[key]) >= 0;
      return true;
    }, true);
  };


    //And when you define the dataSource you have
    this.dataSource.filterPredicate = this.customFilterPredicate();

The customFilter function may appear complex at first glance but essentially utilizes the reduce method to iterate through elements efficiently while returning a single result.

To further illustrate, one can view the process as a simple loop in the given code snippet.

I have provided a StackBlitz example for practical demonstration.

NOTE: It might be necessary to validate against toUpperCase()

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

The CloudWatch logs for a JavaScript Lambda function reveal that its handler is failing to load functions that are defined in external

Hello there, AWS Lambda (JavaScript/TypeScript) is here. I have developed a Lambda handler that performs certain functions when invoked. Let me walk you through the details: import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda' ...

Angular integration problem with aws-amplify when signing up with Google account

I am attempting to integrate AWS-Amplify(^4.3.0) with angular-12 and typescript (4.3.5). I have followed the documentation to configure amplify properly, but when trying to start the app, I encountered some amplify errors as shown below. Warning: D:\G ...

Inability to assign a value to an @input within an Angular project

I recently started using Angular and I'm currently trying to declare an input. Specifically, I need the input to be a number rather than a string in an object within an array. However, I'm encountering difficulties and I can't figure out wha ...

Generic Abstract Classes in TypeScript

In my TypeScript code, I have an abstract generic class with a method that takes a parameter of a class type variable. When I tried to implement the abstract method in a derived class, I noticed that the TypeScript compiler doesn't check the type of t ...

Typescript indicates that an object may be potentially null

I've hit a roadblock where I keep getting warnings that the objects might be null. After searching online and on StackOverflow, I've tried numerous solutions with no luck. My goal is to insert the text "test" into the HTML elements using their ID ...

Sending data from a parent component to a child component using @Input in Angular 16

For some reason, I am struggling to pass the variable someErrorString from the Parent component to the child component page-error. When I check the UI of page-error, it appears blank instead of showing "Hi!" as expected. What could be the issue here? Pare ...

Access values of keys in an array of objects using TypeScript during array initialization

In my TypeScript code, I am initializing an array of objects. I need to retrieve the id parameter of a specific object that I am initializing. vacancies: Array<Vacancy> = [{ id: 1, is_fav: this.favouritesService.favourites.find(fav = ...

Unexpected lack of tree shaking in Angular AOT compiled application

I am developing a module called MyCommonModule that consists of common components, services, etc. This module is designed to be shared across multiple Angular applications. Here is an example of a basic app that imports MyCommonModule, without directly re ...

The module 'AppModule' raised an error due to an unexpected value being imported, specifically 'AngularFireDatabase'. For proper functionality, consider adding a @NgModule annotation

App.Module.ts import { AngularFireDatabase } from 'angularfire2/database'; imports: [ AngularFireDatabase ] I can't seem to figure out why it is requesting me to include a @NgModule annotation when I believe it is unnecessary. My ...

Do Typescript interfaces check method parameters for validation?

interface optionsParameter { url: string; } function DEC(options: optionsParameter){ } DEC(2) //typescript check compilation error let obj:any = { name: "Hello" } obj.DEC = function(options: optionsParameter){} obj.DEC(1); // no compilation ...

Dynamically access nested objects by utilizing an array of strings as a pathway

Struggling to find a solution for accessing nested object properties dynamically? The property path needs to be represented as an array of strings. For example, to retrieve the label, use ['type', 'label'] I'm at a roadblock wit ...

Changing return values with Jest mocks in TypeScript

Here I am again with a very straightforward example. In summary, I require a different response from the mocked class. Below is my basic class that returns an object: class Producer { hello() { return { ...

How can I seamlessly combine CoffeeScript and TypeScript files within a single Node.js project?

When working on a server-side node project utilizing the standard package.json structure, what is the best way to incorporate both Coffeescript and Typescript files? It's crucial that we maintain the availability of npm install, npm test, and npm sta ...

Can projects be published on npm and installed from a specific directory?

Currently working on an Angular library project, I am exploring the possibility of publishing the entire library alongside a pre-built angular application either on gitlab or npm. However, my concern lies in ensuring that when a user installs the library v ...

Using TypeScript with GraphQL Fetch: A Guide

I came across a similar question that almost solved my issue, but it didn't quite work for me because the endpoint I'm using is a graphQL endpoint with an additional nested property called query. For instance, if my query looks like this: const q ...

Incorporate a CSS class name with a TypeScript property in Angular version 7

Struggling with something seemingly simple... All I need is for my span tag to take on a class called "store" from a variable in my .ts file: <span [ngClass]="{'flag-icon': true, 'my_property_in_TS': true}"></span> I&apos ...

What is the most effective way to loop and render elements within JSX?

Trying to achieve this functionality: import React from 'react'; export default class HelloWorld extends React.Component { public render(): JSX.Element { let elements = {"0": "aaaaa"}; return ( ...

What is the best way to import multiple classes from a module folder in Angular2 using TypeScript?

In my Angular2 application, I have organized my modules as follows: /app /content /models resource.ts container.ts entity-type.ts index.ts /services /whatever ...

How to convert form fields into JSON format using Angular 2

Currently, I am in the process of learning angular2 and have encountered a roadblock. I have created a form where the values are populated through JSON. The form consists of both pre-filled fields and text input fields where users can enter data and select ...

Encountering build errors with @angular/cdk and @angular/forms post updating to Angular 11

Upon upgrading to Angular 11, I encountered a series of errors during the build process of my solution: \node_modules\@angular\cdk\coercion\array.d.ts(10,60): error TS1005: Build:',' expected. \node_modules\@ang ...