Search code examples
angularfilterpipeangular-filtersangular-pipe

Dynamic grid filter pipe


I have a generic grid like below

<table class="table">
    <thead>
        <tr role="row" class="headers-filters">
            <th *ngFor="let objGColumn of gridColumns; let idx=index;">
                <dt-column-header [(filter)]="grdFilters[objGColumn.propertyKey]"></dt-column-header>
            </th>
        </tr>
    </thead>
    <tbody>
        <tr role="row" *ngFor="let objRC of recordList | filterColumn: grdFilters; let idx=index;">
            <td class="text-center" *ngFor="let objGColumn of gridColumns; let idx=index;">           
                <span [textContent]="objRC[objGColumn.propertyKey]"></span>
            </td>
        </tr>
    </tbody>
</table>

Where gridColumns will be array of display columns.

dt-column-header is a component which will contain textbox for search column. It has two-way binding property filter which will be filters in grid

filters will be like grdFilters = {LastName: "123", FirstName: "456"}

My pipe is like below. I have taken reference from https://www.code-sample.com/2018/07/angular-6-search-filter-pipe-table-by.html

@Pipe({
  name: 'filterColumn'
})
export class GrdFilterPipe implements PipeTransform {
  transform(items: any, filter: any, defaultFilter: boolean): any {
    if (!filter){
      return items;
    }

    if (!Array.isArray(items)){
      return items;
    }

    if (filter && Array.isArray(items)) {
      let filterKeys = Object.keys(filter);

      if (defaultFilter) {
        return items.filter(item =>
            filterKeys.reduce((x, keyName) =>
                (x && new RegExp(filter[keyName], 'gi').test(item[keyName])) || filter[keyName] == "", true));
      }
      else {
        return items.filter(item => {
          return filterKeys.some((keyName) => {
            return new RegExp(filter[keyName], 'gi').test(item[keyName]) || filter[keyName] == "";
          });
        });
      }
    }
  }
}

My first issue is pipe is not calling when gridFilters change. I had to make it impure which will have effect on performance.

Another issue is pipe works fine with {LastName: "123"} OR {FirstName: "456"}

But not working with {LastName: "123", FirstName: "456"} OR {LastName: "", FirstName: "456"} OR {LastName: "123", FirstName: ""}


Solution

  • Quick answer:

    "My first issue is pipe is not calling when gridFilters change":

    RECOMENDED: Leave your pipe as a pure pipe and reassign gridFilters instead of modifying it. In other words, instead of using ngModel in your dt-column-header component, use input event and reassign gridFilters variable.

    OR (NOT RECOMMENDED): Make your pipe an impure pipe by adding pure: false in the @Pipe decorator:

    @Pipe({
      name: 'searchFilter',
      pure: false
    })
    

    See this StackBlitz DEMO with a working example code.

    "Another issue is pipe works fine with... But not working with":

    The problem is with the pipe's code:

    return items.filter(item => {
      return filterKeys.some((keyName) => {
        return new RegExp(filter[keyName], 'gi').test(item[keyName]) || filter[keyName] == "";
      });
    });
    

    Apparently you didn't get the outcome you've wanted to get. The problem that you didn't mentioned the result that you want to get, what do you mean working/not working? edit your question with your needs and I'll update my answer.

    Detailed answer

    explaining pure/impure pipes:

    Your pipe is a pure pipe (default pipe type when not using metadata pure: false in @Pipe decorator). A pure pipe will execute only when there is either a change to a primitive input value (String, Number, Boolean, Symbol) or a changed object reference (Date, Array, Function, Object).

    Angular Docs - Pure pipes

    Therefore, you have 2 options as a solution:

    Solution 1 (recomended):

    In order for your pipe to call again when gridFilters changes, you need to reassign it instead of modifying it. In other words, instead of using ngModel in your dt-column-header component, use input event and reassign gridFilters variable.

    Solution 2 (not recomended):

    Make your pipe an impure pipe by adding pure: false in the @Pipe decorator:

    @Pipe({
      name: 'searchFilter',
      pure: false
    })
    

    This time, your pipe will execute in every component change detection cycle. This is also why this solution is not recommend because it will be called often, as often as every keystroke or mouse-move.

    Angular Docs - Impure pipes says:

    With that concern in mind, implement an impure pipe with great care. An expensive, long-running pipe could destroy the user experience.