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

What is the equivalent of Typescript's Uint8Array and Uint16Array in Python?

new Uint8Array(new Uint16Array([64]).buffer) How can I achieve a similar data structure in pure Python? What is the equivalent of Uint8Array/Uint16Array? I am extracting a buffer from a Uint16Array type here and converting it to a Uint8Array, but I am un ...

What causes the discrepancy in values displayed by enums in TypeScript when assigned integers in reverse order?

Recently diving into the world of TypeScript, I've been experimenting with different types in this language. One interesting data type I played with is enums. Here's an example of code I used: enum colors {red=1,green=0,blue,white}; console.lo ...

Tips for passing an object as an argument to a function with optional object properties in TypeScript

Consider a scenario where I have a function in my TypeScript API that interacts with a database. export const getClientByEmailOrId = async (data: { email: any, id: any }) => { return knex(tableName) .first() .modify((x: any) => { if ( ...

Include asterisk symbol at the end of a web address following a designated keyword

I currently have a URL structured in the following way: https://localhost:8080/user/login But, there is an option to manually add query parameters which could result in a URL like this. https://localhost:8080/user/login?ten=123456 This leads me to seek ...

Encountered an error while trying to update information in Angular

I have been working on a project involving a .NET Core Web API and Angular 11 (Full-Stack) project. I have successfully managed to add data to the database in my back-end, but I am encountering an issue when trying to update the data. Below is a snippet o ...

Oops! There seems to be an issue with the code: "TypeError: this

I am just starting out with Angular. Currently, I need to assign a method to my paginator.getRangeLabel (I want to use either a standard label or a suffixed one depending on certain conditions): this.paginator._intl.getRangeLabel = this.getLabel; The cod ...

Is it possible to control Firestore's data using an Angular service?

I am currently developing a basic example (Tour of Heroes) in Angular.io utilizing Firestore. In order to enhance readability and organization, the sections related to Firestore were segregated within the service and then referred in each component. Howev ...

Having trouble accessing Vuex's getter property within a computed property

Can you help me troubleshoot an issue? When I call a getter inside a computed property, it is giving me the following error message: TS2339: Property 'dictionary' does not exist on type 'CreateComponentPublicInstance{}, {}, {}, {}, {}, Com ...

Having trouble locating the 'tsc-watch' module due to the missing 'typescript/bin/tsc' when running the TypeScript compiler

Encountering the error "Cannot find module 'typescript/bin/tsc' when attempting to run tsc-watch yarn tsc-watch --noClear -p tsconfig.json yarn run v1.22.19 $ /Users/jason/Work/VDQ/VDQApp/node_modules/.bin/tsc-watch --noClear -p tsconfig.json Ca ...

Error: Unable to cast value to an array due to validation failure

I'm currently working on integrating Typegoose with GrqphQL, MongoDB, and Nest.js for a project. My goal is to create a mutation that will allow users to create a post. I have set up the model, service, and resolver for a simple Post. However, when I ...

Error: 'next' is not defined in the beforeRouteUpdate method

@Component({ mixins: [template], components: { Sidebar } }) export default class AppContentLayout extends Vue { @Prop({default: 'AppContent'}) title: string; @Watch('$route') beforeRouteUpdateHandler (to: Object, fro ...

Error message: Custom binding handler failed: 'Flatpickr' is not a valid constructor

Trying my hand at creating a custom binding handler in knockout for Flatpickr has hit a snag. Upon attempting to use it, an error is thrown: Uncaught TypeError: Unable to process binding "datetimepicker: function (){return startDate }" Message: Flatpickr ...

Using Jest and TypeScript to mock the return value of react-oidc-context

For our project, we utilize react-oidc-context to handle user authentication using oidc-client-ts under the hood. The useAuth function provided by react-oidc-context gives us access to important information such as isAuthenticated, isLoading, and the auth ...

Developing a custom React component library using Webpack and Typescript, however encountering issues with Webpack consistently bundling React

I'm currently in the process of developing an external component library using Webpack and TypeScript. Although it compiles without any issues, I encounter an error when attempting to use the library: Invalid hook call. Hooks can only be called ins ...

Storing the state of DevExtreme DataGrid in Angular

Currently, I have integrated the DevExtreme DataGrid widget into my Angular application. Here is a snippet of how my DataGrid is configured: <dx-data-grid id="gridContainer" [dataSource]="employees" [allowColumnReordering]="true" [allo ...

Managing input and output using a collaborative service

I've been working on refactoring some code that contains a significant amount of duplicate methods between two components. Component A is a child of component B, and they can be separate instances as intended. The issue I'm facing revolves around ...

Define the output type of an arrow function within an interface signature

How can I inform typescript that I will be utilizing an interface containing a function called "foo" which always returns a string. The implementation of the function will be specified by the object implementing the interface. For example: export interfac ...

Limit access to a specific route within the URL

Is there a way to ensure that users can access and download myfile.pdf without being able to see or access /root, /folder, or /subdirectory in the URL? This functionality can be implemented using HTML, Angular 6, ReactJS, or similar frameworks. ...

Strategies for successfully passing and showcasing the result of a calculation on a fresh view or component within Angular

I have developed a basic calculator application with two main components: Calculator and Result. The Angular router is used to navigate between these components. Now, I am looking for a way to dynamically display the calculation result from the Calculator ...

NPM Package: Accessing resources from within the package

My Current Setup I have recently developed and published an npm package in typescript. This package contains references to font files located within a folder named public/fonts internally. Now, I am in the process of installing this package into an Angul ...