[angular] Angular 2 filter/search list

I'm looking for the angular 2 way to do this.

I simply have a list of items, and I want to make an input whos job is to filter the list.

<md-input placeholder="Item name..." [(ngModel)]="name"></md-input>

<div *ngFor="let item of items">
{{item.name}}
</div>

What's the actual way of doing this in Angular 2? Does that requires a pipe?

This question is related to angular

The answer is


You have to manually filter result based on change of input each time by keeping listener over input event. While doing manually filtering make sure you should maintain two copy of variable, one would be original collection copy & second would be filteredCollection copy. The advantage for going this way could save your couple of unnecessary filtering on change detection cycle. You may see a more code, but this would be more performance friendly.

Markup - HTML Template

<md-input #myInput placeholder="Item name..." [(ngModel)]="name" (input)="filterItem(myInput.value)"></md-input>

<div *ngFor="let item of filteredItems">
   {{item.name}}
</div>

Code

assignCopy(){
   this.filteredItems = Object.assign([], this.items);
}
filterItem(value){
   if(!value){
       this.assignCopy();
   } // when nothing has typed
   this.filteredItems = Object.assign([], this.items).filter(
      item => item.name.toLowerCase().indexOf(value.toLowerCase()) > -1
   )
}
this.assignCopy();//when you fetch collection from server.

<md-input placeholder="Item name..." [(ngModel)]="name" (keyup)="filterResults()"></md-input>

<div *ngFor="let item of filteredValue">
{{item.name}}
</div>

  filterResults() {
    if (!this.name) {
      this.filteredValue = [...this.items];
    } else {
      this.filteredValue = [];
      this.filteredValue = this.items.filter((item) => {
        return item.name.toUpperCase().indexOf(this.name.toUpperCase()) > -1;
      });
    }
 }

Don't do any modification on 'items' array(list of items from which results are filtered). When searched item 'name' is empty return the complete list of 'items', if not compare the 'name' with every 'name' in the 'items ' array and filter out only the name that is present in 'items' array and store it in the 'filteredValue'.


data

names = ['Prashobh','Abraham','Anil','Sam','Natasha','Marry','Zian','karan']

You can achieve this by creating a simple pipe

<input type="text" [(ngModel)]="queryString" id="search" placeholder="Search to type">

Pipe

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

@Pipe({
    name: 'FilterPipe',
})
export class FilterPipe implements PipeTransform {
    transform(value: any, input: string) {
        if (input) {
            input = input.toLowerCase();
            return value.filter(function (el: any) {
                return el.toLowerCase().indexOf(input) > -1;
            })
        }
        return value;
    }
}

This will filter the result based on the search term

More Info


try this html code

<md-input #myInput placeholder="Item name..." [(ngModel)]="name"></md-input>

<div *ngFor="let item of filteredItems | search: name">
   {{item.name}}
</div>

use search pipe

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

@Pipe({
  name: 'search'
})
export class SearchPipe implements PipeTransform {

  transform(value: any, args?: any): any {

    if(!value)return null;
    if(!args)return value;

    args = args.toLowerCase();

    return value.filter(function(item){
        return JSON.stringify(item).toLowerCase().includes(args);
    });
}

}

This code nearly worked for me...but I wanted a multiple element filter so my mods to the filter pipe are below:

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

@Pipe({ name: 'jsonFilterBy' })
@Injectable()
export class JsonFilterByPipe implements PipeTransform {

  transform(json: any[], args: any[]): any[] {
    const searchText = args[0];
    const jsonKey = args[1];
    let jsonKeyArray = [];

    if (searchText == null || searchText === 'undefined') { return json; }

    if (jsonKey.indexOf(',') > 0) {
        jsonKey.split(',').forEach( function(key) {
            jsonKeyArray.push(key.trim());
        });
    } else {
        jsonKeyArray.push(jsonKey.trim());
    }

    if (jsonKeyArray.length === 0) { return json; }

    // Start with new Array and push found objects onto it.
    let returnObjects = [];
    json.forEach( function ( filterObjectEntry ) {

        jsonKeyArray.forEach( function (jsonKeyValue) {
            if ( typeof filterObjectEntry[jsonKeyValue] !== 'undefined' &&
            filterObjectEntry[jsonKeyValue].toLowerCase().indexOf(searchText.toLowerCase()) > -1 ) {
                // object value contains the user provided text.
                returnObjects.push(filterObjectEntry);
                }
            });

    });
    return returnObjects;
  }
} 

Now, instead of

jsonFilterBy:[ searchText, 'name']

you can do

jsonFilterBy:[ searchText, 'name, other, other2...']

Slight modification to @Mosche answer, for handling if there exist no filter element.

TS:

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

@Pipe({
    name: 'filterFromList'
})
export class FilterPipe implements PipeTransform {
    public transform(value, keys: string, term: string) {

        if (!term) {
            return value
        }
        let res = (value || []).filter((item) => keys.split(',').some(key => item.hasOwnProperty(key) && new RegExp(term, 'gi').test(item[key])));
        return res.length ? res : [-1];

    }
}

Now, in your HTML you can check via '-1' value, for no results.

HTML:

<div *ngFor="let item of list | filterFromList: 'attribute': inputVariableModel">
            <mat-list-item *ngIf="item !== -1">
                <h4 mat-line class="inline-block">
                 {{item}}
                </h4>
            </mat-list-item>
            <mat-list-item *ngIf="item === -1">
                No Matches
            </mat-list-item>
        </div>

Pipes in Angular 2+ are a great way to transform and format data right from your templates.

Pipes allow us to change data inside of a template; i.e. filtering, ordering, formatting dates, numbers, currencies, etc. A quick example is you can transfer a string to lowercase by applying a simple filter in the template code.

List of Built-in Pipes from API List Examples

{{ user.name | uppercase }}

Example of Angular version 4.4.7. ng version


Custom Pipes which accepts multiple arguments.

HTML « *ngFor="let student of students | jsonFilterBy:[searchText, 'name'] "
TS   « transform(json: any[], args: any[]) : any[] { ... }

Filtering the content using a Pipe « json-filter-by.pipe.ts

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

@Pipe({ name: 'jsonFilterBy' })
@Injectable()
export class JsonFilterByPipe implements PipeTransform {

  transform(json: any[], args: any[]) : any[] {
    var searchText = args[0];
    var jsonKey = args[1];

    // json = undefined, args = (2) [undefined, "name"]
    if(searchText == null || searchText == 'undefined') return json;
    if(jsonKey    == null || jsonKey    == 'undefined') return json;

    // Copy all objects of original array into new Array.
    var returnObjects = json;
    json.forEach( function ( filterObjectEntery ) {

      if( filterObjectEntery.hasOwnProperty( jsonKey ) ) {
        console.log('Search key is available in JSON object.');

        if ( typeof filterObjectEntery[jsonKey] != "undefined" && 
        filterObjectEntery[jsonKey].toLowerCase().indexOf(searchText.toLowerCase()) > -1 ) {
            // object value contains the user provided text.
        } else {
            // object didn't match a filter value so remove it from array via filter
            returnObjects = returnObjects.filter(obj => obj !== filterObjectEntery);
        }
      } else {
        console.log('Search key is not available in JSON object.');
      }

    })
    return returnObjects;
  }
}

Add to @NgModule « Add JsonFilterByPipe to your declarations list in your module; if you forget to do this you'll get an error no provider for jsonFilterBy. If you add to module then it is available to all the component's of that module.

@NgModule({
  imports: [
    CommonModule,
    RouterModule,
    FormsModule, ReactiveFormsModule,
  ],
  providers: [ StudentDetailsService ],
  declarations: [
    UsersComponent, UserComponent,

    JsonFilterByPipe,
  ],
  exports : [UsersComponent, UserComponent]
})
export class UsersModule {
    // ...
}

File Name: users.component.ts and StudentDetailsService is created from this link.

import { MyStudents } from './../../services/student/my-students';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { StudentDetailsService } from '../../services/student/student-details.service';

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: [ './users.component.css' ],

  providers:[StudentDetailsService]
})
export class UsersComponent implements OnInit, OnDestroy  {

  students: MyStudents[];
  selectedStudent: MyStudents;

  constructor(private studentService: StudentDetailsService) { }

  ngOnInit(): void {
    this.loadAllUsers();
  }
  ngOnDestroy(): void {
    // ONDestroy to prevent memory leaks
  }

  loadAllUsers(): void {
    this.studentService.getStudentsList().then(students => this.students = students);
  }

  onSelect(student: MyStudents): void {
    this.selectedStudent = student;
  }

}

File Name: users.component.html

<div>
    <br />
    <div class="form-group">
        <div class="col-md-6" >
            Filter by Name: 
            <input type="text" [(ngModel)]="searchText" 
                   class="form-control" placeholder="Search By Category" />
        </div>
    </div>

    <h2>Present are Students</h2>
    <ul class="students">
    <li *ngFor="let student of students | jsonFilterBy:[searchText, 'name'] " >
        <a *ngIf="student" routerLink="/users/update/{{student.id}}">
            <span class="badge">{{student.id}}</span> {{student.name | uppercase}}
        </a>
    </li>
    </ul>
</div>

Search by multiple fields

Assuming Data:

items = [
  {
    id: 1,
    text: 'First item'
  },
  {
    id: 2,
    text: 'Second item'
  },
  {
    id: 3,
    text: 'Third item'
  }
];

Markup:

<input [(ngModel)]="query">
<div *ngFor="let item of items | search:'id,text':query">{{item.text}}</div>

Pipe:

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

@Pipe({
  name: 'search'
})
export class SearchPipe implements PipeTransform {
  public transform(value, keys: string, term: string) {

    if (!term) return value;
    return (value || []).filter(item => keys.split(',').some(key => item.hasOwnProperty(key) && new RegExp(term, 'gi').test(item[key])));

  }
}

One line for everything!


HTML

<input [(ngModel)] = "searchTerm" (ngModelChange) = "search()"/>
<div *ngFor = "let item of items">{{item.name}}</div>

Component

search(): void {
    let term = this.searchTerm;
    this.items = this.itemsCopy.filter(function(tag) {
        return tag.name.indexOf(term) >= 0;
    }); 
}

Note that this.itemsCopy is equal to this.items and should be set before doing the search.


In angular 2 we don't have pre-defined filter and order by as it was with AngularJs, we need to create it for our requirements. It is time killing but we need to do it, (see No FilterPipe or OrderByPipe). In this article we are going to see how we can create filter called pipe in angular 2 and sorting feature called Order By. Let's use a simple dummy json data array for it. Here is the json we will use for our example

First we will see how to use the pipe (filter) by using the search feature:

Create a component with name category.component.ts

_x000D_
_x000D_
    import { Component, OnInit } from '@angular/core';_x000D_
    @Component({_x000D_
      selector: 'app-category',_x000D_
      templateUrl: './category.component.html'_x000D_
    })_x000D_
    export class CategoryComponent implements OnInit {_x000D_
_x000D_
      records: Array<any>;_x000D_
      isDesc: boolean = false;_x000D_
      column: string = 'CategoryName';_x000D_
      constructor() { }_x000D_
_x000D_
      ngOnInit() {_x000D_
        this.records= [_x000D_
          { CategoryID: 1,  CategoryName: "Beverages", Description: "Coffees, teas" },_x000D_
          { CategoryID: 2,  CategoryName: "Condiments", Description: "Sweet and savory sauces" },_x000D_
          { CategoryID: 3,  CategoryName: "Confections", Description: "Desserts and candies" },_x000D_
          { CategoryID: 4,  CategoryName: "Cheeses",  Description: "Smetana, Quark and Cheddar Cheese" },_x000D_
          { CategoryID: 5,  CategoryName: "Grains/Cereals", Description: "Breads, crackers, pasta, and cereal" },_x000D_
          { CategoryID: 6,  CategoryName: "Beverages", Description: "Beers, and ales" },_x000D_
          { CategoryID: 7,  CategoryName: "Condiments", Description: "Selishes, spreads, and seasonings" },_x000D_
          { CategoryID: 8,  CategoryName: "Confections", Description: "Sweet breads" },_x000D_
          { CategoryID: 9,  CategoryName: "Cheeses",  Description: "Cheese Burger" },_x000D_
          { CategoryID: 10, CategoryName: "Grains/Cereals", Description: "Breads, crackers, pasta, and cereal" }_x000D_
         ];_x000D_
         // this.sort(this.column);_x000D_
      }_x000D_
    }
_x000D_
<div class="col-md-12">_x000D_
  <table class="table table-responsive table-hover">_x000D_
    <tr>_x000D_
      <th >Category ID</th>_x000D_
      <th>Category</th>_x000D_
      <th>Description</th>_x000D_
    </tr>_x000D_
    <tr *ngFor="let item of records">_x000D_
      <td>{{item.CategoryID}}</td>_x000D_
      <td>{{item.CategoryName}}</td>_x000D_
      <td>{{item.Description}}</td>_x000D_
    </tr>_x000D_
  </table>_x000D_
</div>
_x000D_
_x000D_
_x000D_

2.Nothing special in this code just initialize our records variable with a list of categories, two other variables isDesc and column are declared which we will use for sorting latter. At the end added this.sort(this.column); latter we will use, once we will have this method.

Note templateUrl: './category.component.html', which we will create next to show the records in tabluar format.

For this create a HTML page called category.component.html, whith following code:

3.Here we use ngFor to repeat the records and show row by row, try to run it and we can see all records in a table.

Search - Filter Records

Say we want to search the table by category name, for this let's add one text box to type and search

_x000D_
_x000D_
<div class="form-group">_x000D_
  <div class="col-md-6" >_x000D_
    <input type="text" [(ngModel)]="searchText" _x000D_
           class="form-control" placeholder="Search By Category" />_x000D_
  </div>_x000D_
</div>
_x000D_
_x000D_
_x000D_

5.Now we need to create a pipe to search the result by category because filter is not available as it was in angularjs any more.

Create a file category.pipe.ts and add following code in it.

_x000D_
_x000D_
    import { Pipe, PipeTransform } from '@angular/core';_x000D_
    @Pipe({ name: 'category' })_x000D_
    export class CategoryPipe implements PipeTransform {_x000D_
      transform(categories: any, searchText: any): any {_x000D_
        if(searchText == null) return categories;_x000D_
_x000D_
        return categories.filter(function(category){_x000D_
          return category.CategoryName.toLowerCase().indexOf(searchText.toLowerCase()) > -1;_x000D_
        })_x000D_
      }_x000D_
    }
_x000D_
_x000D_
_x000D_

6.Here in transform method we are accepting the list of categories and search text to search/filter record on the list. Import this file into our category.component.ts file, we want to use it here, as follows:

_x000D_
_x000D_
import { CategoryPipe } from './category.pipe';_x000D_
@Component({      _x000D_
  selector: 'app-category',_x000D_
  templateUrl: './category.component.html',_x000D_
  pipes: [CategoryPipe]   // This Line       _x000D_
})
_x000D_
_x000D_
_x000D_

7.Our ngFor loop now need to have our Pipe to filter the records so change it to this.You can see the output in image below

enter image description here


You can also create a search pipe to filter results:

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

@Pipe({
  name : 'searchPipe',
})
export class SearchPipe implements PipeTransform {
  public transform(value, key: string, term: string) {
    return value.filter((item) => {
      if (item.hasOwnProperty(key)) {
        if (term) {
          let regExp = new RegExp('\\b' + term, 'gi');
          return regExp.test(item[key]);
        } else {
          return true;
        }
      } else {
        return false;
      }
    });
  }
}

Use pipe in HTML :

<md-input placeholder="Item name..." [(ngModel)]="search" ></md-input>
<div *ngFor="let item of items | searchPipe:'name':search ">
  {{item.name}}
</div>

Currently ng2-search-filter simplify this works.

By directive

<tr *ngFor="let item of items | filter:searchText">
  <td>{{item.name}}</td>
</tr>

Or programmatically

let itemsFiltered = new Ng2SearchPipe().transform(items, searchText);

Practical example: https://angular-search-filter.stackblitz.io