Red Cricket
Red Cricket

Reputation: 10470

Writing a custom table filter in Angular 6

I wanted to add a custom table filter to my angular code. Did web search and found this blog post:

https://www.code-sample.com/2018/07/angular-6-search-filter-pipe-table-by.html

It works pretty well and here the pipe code:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'gridFilter'
})

/*
 I got this code from here:
 https://www.code-sample.com/2018/07/angular-6-search-filter-pipe-table-by.html
*/

export class GridFilterPipe 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] == "";
          });
        });
      }
    }
  }
}

It works pretty well for most of my needs except this one table that is this html:

  <div class="col-md-4" *ngIf="element == 'location'">
    <div class="spacer"></div>
    <div class="panel panel--vibblue panel--raised">{{ element | titlecase }}</div>
    <div class="responsive-table panel--raised">
      <table class="table table--bordered table--hover approvals-table" id="location-table">
        <thead>
        <tr>
          <th class="sortable">{{ element | titlecase }} Name <span class="sort-indicator icon-chevron-down"></span></th>
          <th class="sortable">Site <span class="sort-indicator icon-chevron-down"></span></th>
          <th>Action</th>
        </tr>
        </thead>
        <tbody>
        <ng-container *ngFor="let el of elements | gridFilter: {name: searchText, site:searchText}">
          <tr>
            <td>{{el.name}}</td>
            <td>{{ getSiteName(el.site) }}</td>
            <td>
              <a><span class="icon-trash" (click)="deleteElement(el.id, el.name)"></span></a>
            </td><td>
            <a><span class="icon-pencil" (click)="editElement(el)"></span></a>
          </td>
          </tr>
        </ng-container>
        </tbody>
      </table>
    </div>
  </div>

The issue with the above table is this. To display the site name of a location I use this html ...

<td>{{ getSiteName(el.site) }}</td>

... which calls a method getSiteName which takes the id of a site and returns the site's name. I do not know how to set my gridFilter. So if I try to search for a site name my gridFilter doesn't find the site.

Here is my stackblitz for this: https://stackblitz.com/edit/angular-damcul

** UPDATE **

I have updated my stackblitz to actually illustrate the problem I am having. Here is a screen shot of the stackblitz app:

enter image description here

I can search for locations and here is the screen shot of me searching for 'loc_2':

enter image description here

But I can not search for sites. Here the screen shot of me searching for any 'site'.

enter image description here

Upvotes: 3

Views: 3802

Answers (1)

Akber Iqbal
Akber Iqbal

Reputation: 15031

made some changes:

  • in your shared code in the question, you had used filter named grdFilter, in your stackblitz it was 'gridFilter'
  • implementation of getSiteName was added, which takes in the object and returns the blog name (there is no property named 'site' in our data)
  • in your shared code in the question, you had

<ng-container *ngFor="let el of customerData | gridFilter: {name: searchText, site:searchText}">

  • since there is no property named 'site' in our data, we changed it to

<ng-container *ngFor="let el of customerData | grdFilter: {name: searchText, blog:searchText}">

relevant hello.componenet.ts:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'hello',
  template: `<input [(ngModel)]="searchText" placeholder="Search.." class="advancedSearchTextbox">
<p></p>
<table *ngFor="let emp of customerData | grdFilter: {name: searchText, Age:searchText,  blog: searchText}; let i=index;">
  <tr>
    <td style="width: 5%;">{{i +1}}</td>
    <td style="width: 10%;">{{emp.name}}</td>
    <td style="width: 5%;">{{emp.Age}}</td>
    <td style="width: 15%;">{{emp.blog}}</td>
  </tr>
</table>
<hr/>
<table class="table table--bordered table--hover approvals-table" id="location-table">
  <thead>
    <tr>
      <th class="sortable">{{ element | titlecase }} Name <span class="sort-indicator icon-chevron-down"></span></th>
      <th class="sortable">Site <span class="sort-indicator icon-chevron-down"></span></th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <ng-container *ngFor="let el of customerData | grdFilter: {name: searchText, blog:searchText}">
      <tr>
        <td>{{el.name}}</td>
        <td>{{ getSiteName(el) }}</td>
        <td>
          <a><span class="icon-trash" (click)="deleteElement(el.id, el.name)"></span></a>
        </td><td>
        <a><span class="icon-pencil" (click)="editElement(el)"></span></a>
      </td>
      </tr>
    </ng-container>
  </tbody>
</table>
`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
  public searchText: string;
  public customerData: any;

  @Input() name: string;

  constructor() { }

  ngOnInit() {
    this.customerData = [
      { "name": 'Anil kumar', "Age": 34, "blog": 'https://code-view.com' },
      { "name": 'Sunil Kumar Singh', "Age": 28, "blog": 'https://code-sample.xyz' },
      { "name": 'Sushil Singh', "Age": 24, "blog": 'https://code-sample.com' },
      { "name": 'Aradhya Singh', "Age": 5, "blog": 'https://code-sample.com' },
      { "name": 'Reena Singh', "Age": 28, "blog": 'https://code-sample.com' },
      { "name": 'Alok Singh', "Age": 35, "blog": 'https://code-sample.com' },
      { "name": 'Dilip Singh', "Age": 34, "blog": 'https://code-sample.com' }];
  }
  getSiteName(passedObj) {
    return passedObj.blog;
  }
}

update #1 in light of questioner's comment & updated question:

if you go to the grd-pipe.ts file, and do console.log(items,filter), you'll see that the array that we're working on inside the custom pipe is the locations array, which has the 'sites' column values as 1, 2 or 3. The site name which you have fetched for the UI from sites array is not part of the locations array and hence can't be processed by the pipe.

So, we create a new field siteName in the locations array where we store the site name (from sites array), now since the field is available inside the pipe, the pipe can search on it also.

relevant changes in TS

ngOnInit(){
for(var i=0;i<this.locations.length;i++){
        this.locations[i].siteName = this.sites.find(s => s.id === this.locations[i].site).name;
      }
}

relevant changes in HTML the added field siteName added to the filter:

<table class="table table--bordered table--hover approvals-table" id="location-table">
  <tbody>
    <ng-container *ngFor="let loc of locations | grdFilter: {name: searchText, siteName:searchText}; let i=index">
      <tr>
        <td style="width: 5%;">{{i +1}}</td>
        <td style="width: 10%;">{{loc.name}}</td>
        <td style="width: 5%;">{{getSiteName(loc.site)}}</td>
      </tr>
    </ng-container>
  </tbody>
</table>

working stackblitz here is also updated

Upvotes: 3

Related Questions