Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

January 26, 2016

Creating a Filter Pipe in Angular 2

Filed under: Angular @ 4:23 pm
Tags: , , ,

Angular 1.x has filters. Angular 2 has pipes, which fulfill a similar purpose. However, there are some key filters in Angular 1.x, such as the "filter filter", that don’t have an equivalent Angular 2 feature. This post details how to build a filter pipe in Angular 2.

Angular 1.x

In Angular 1.x, we define an input box for a user to enter filter text. We then filter a list of items by that text with one simple clause as shown below:

<div class="row">
    <div class="col-md-2">Filter by:</div>
    <div class="col-md-4"><input type="text" ng-model="listFilter" /></div>
</div>

<div class="table-responsive">
    <table class="table">
        <tbody>
            <tr ng-repeat="movie in vm.movies | filter : {title:listFilter}">
                <td>{{ movie.title}}</td>
                <td>{{ movie.director }}</td>
                <td>{{ movie.mpaa | uppercase}}</td>
            </tr>
        </tbody>
    </table>
</div>

Easy breezy.

Angular 2

Angular 2 provides pipes for this same purpose. But with no "filter" pipe in Angular 2, we have to build our own pipe component.

Converting the above code into Angular 2 results in:

<div class="row">
    <div class="col-md-2">Filter by:</div>
    <div class="col-md-4"><input type="text" #listFilter (keyup)="0" /></div>
</div>

<table class="table">
    <tbody>
        <tr *ngFor="#movie of movies | movieFilter:listFilter.value" >
             <td>{{ movie.title}}</td>
            <td>{{ movie.director }}</td>
            <td>{{ movie.mpaa | uppercase}}</td>
        </tr>
    </tbody>
</table>

Hmmm. That does not look that different!

A few things you’ll notice here:

  • The local variable, listFilter, is now prefixed with a hash symbol (#) to explicitly identify it as a local variable.
    • This variable defines an HTML element, so to obtain the value, we need to access listFilter.value.
  • The (keyup)="0" syntax sets up an event listener for the keyup event. There is no event handler needed for this event, so it is just set to "0".
  • The filter clause is defined here as movieFilter and we are filtering on the value of the listFilter variable. So our custom pipe is used just like the built-in pipes.

The key is creating the movieFilter pipe. In Angular 1.x, the "filter filter" was built in. In Angular 2, there are some built-in filters, but not a general list filter. So we need to build our own.

The purpose of a pipe is to take in a value, filter that value, and return the filtered result. In this example, the value we are taking in is a list of movies. We filter the movies to only those with a title containing the user-entered text. And we return the filtered result.

Here is the code for the movieFilter. The description follows.

import {Pipe, PipeTransform} from ‘angular2/core’;

@Pipe({
    name: ‘movieFilter’
})
export class MovieFilterPipe implements PipeTransform {

    transform(value: any, args: string[]): any {

       let filter = args[0].toLocaleLowerCase();
       return filter ? value.filter(movie=> movie.title.toLocaleLowerCase().indexOf(filter) != -1) : value;
    }
}

1) First we import the Pipe and PipeTransform. This is required to build any pipe.

2) We define a Pipe decorator. This tells Angular that this code is a pipe component. And we give the pipe a name: movieFilter. This is the name used in the HTML as part of a template expression.

3) We create a class that provides the processing for our pipe. We export the class so it can be imported by our movie component. And we implement the PipeTransform interface for our tooling. Note that implementing this interface is optional. With the @Pipe decorator, Angular already expects to find a transform method.

4) The PipeTransform interface requires implementing a transform method. This method has two parameters:

  • value: The value being filtered. In our example, this is the list of movies.
  • args: An optional array of parameters, one for each parameter passed to the pipe. In our example, we are passing listFilter.value.

5) We then define a result array which will contain the movies that match the filter criteria.

6) We create a variable to hold the passed in parameter. We use toLocaleLowerCase() so that the filtering won’t be case sensitive.

7) We check the filter variable. If there is no filter defined, we simply return the original value. No need to iterate the list. Otherwise we use the JavaScript filter function to filter the list and return it.

NOTE: This code could be simplified using regular expressions.

Lastly, we need the component:

import {Component} from ‘angular2/core’;
import {MovieFilterPipe} from ‘../common/movieFilter.pipe’

@Component({
    selector: ‘mh-movie-list’,
    templateUrl: ‘app/movies/movieListView.html’,
    styleUrls: [‘node_modules/bootstrap/dist/css/bootstrap.css’],
    pipes: [MovieFilterPipe]
})
export class MovieListComponent {

}

This is the component associated with the movie list view. The two critical steps for the pipe are:

1) Import the MovieFilterPipe.

2) Identify the pipe using the @Component decorator’s pipes array. This provides the list of all custom pipes available for the template.

For more information on Angular 1.x vs Angular 2, see "Angular 1.x to Angular 2 Quick Reference".

For a sample application, see "Angular: 3 Flavors" which has the same application in Angular 1.x, Angular 1.x with TypeScript, and Angular 2.

Enjoy!

13 Comments

  1.   Joey Robert — March 3, 2016 @ 11:52 am    Reply

    It’d be good to implement a more generic filter pipe, so you can use a similar syntax to Angular 1. Here’s what I’ve got to that end (unsure on how the formatting turns out).

    import {Pipe, PipeTransform} from ‘angular2/core’;

    @Pipe({
    name: ‘filter’
    })
    export default class FilterPipe implements PipeTransform {
    transform(value: any, args: string[]): any {
    let filter = args[0];

    if (filter && Array.isArray(value)) {
    let filterKeys = Object.keys(filter);
    return value.filter(item =>
    filterKeys.reduce((memo, keyName) =>
    memo && item[keyName] === filter[keyName], true));
    } else {
    return value;
    }
    }
    }

    •   Francisco Lagos — July 21, 2016 @ 8:34 am    Reply

      Hi,Thank you for this article. Don’t forget the toLowerCase() method.

      transform(value: any, args: string[]): any {
      let filter = args[0];

      if (filter && Array.isArray(value)) {
      let filterKeys = Object.keys(filter);
      return value.filter(item =>
      filterKeys.reduce((memo, keyName) =>
      memo && item[keyName].toLowerCase() === filter[keyName].toLowerCase(), true));
      } else {
      return value;
      }
      }

  2.   Joey Robert — March 3, 2016 @ 1:21 pm    Reply

    Or better yet, a plunker: http://plnkr.co/edit/UbHxQNjV1G8kXsIY2GyF?p=preview

    •   deborahk — April 14, 2016 @ 11:15 am    Reply

      Thanks, Joey!

  3.   Alex Holtry — December 1, 2016 @ 3:31 pm    Reply

    I made some corrections but this is what I ended up with for a generic filter pipe:

    export class Filter {
    transform(valueArray: any, term: any) {
    if (term) {
    let filterKeys = Object.keys(valueArray[0]);
    return valueArray.filter((item: any) =>
    filterKeys.reduce((memo, keyName) =>
    memo && item[keyName].toLowerCase().indexOf(term.toLowerCase()) > -1, true));
    } else {
    return valueArray;
    }
    }
    }

    •   deborahk — December 6, 2016 @ 11:37 am    Reply

      Thanks for posting!

  4.   ehsan — February 9, 2017 @ 5:01 pm    Reply

    Nice article,informative

  5.   new2Angular — May 4, 2017 @ 6:46 pm    Reply

    I am looking to search my data table with global filter. Lets say my structure is like below:

    interface Istudent
    {
    name:string;
    address:string;
    city:string;
    }
    Using pipes i would like to search all the properties that are matching with my filter and return back. Using above example, i could query on only one property. Can someone help please?

    •   deborahk — May 8, 2017 @ 2:32 pm    Reply

      Your best bet for these types of questions is to use stackoverflow. There are lots of experts there just waiting to answer your questions. Plus they often provide sample code.

      To filter on multiple values, you just need to *and* them similar to this:

      return value.filter((product: IProduct) => (field1Filter ? product.field1.toLocaleLowerCase().indexOf(field1Filter) !== -1 : true)
      && (field2Filter ? product.field2.toLocaleLowerCase().indexOf(field2Filter) !== -1 : true)
      && (field3Filter ? product.field3.toLocaleLowerCase().indexOf(field3Filter) !== -1 : true)
      && (field4Filter ? product.field4.toLocaleLowerCase().indexOf(field4Filter) !== -1 : true))

  6.   Laszlo — May 29, 2017 @ 3:10 pm    Reply

    Hi All,

    Is this pipe able to working with pagination?

    •   deborahk — June 1, 2017 @ 5:53 am    Reply

      If you have enough data that it requires pagination, then you most likely don’t want to use a pipe. They don’t perform well with large amounts of data.

      See the Appendix of this chapter of the documentation: https://angular.io/docs/ts/latest/guide/pipes.html

  7.   Sharanagouda — June 1, 2017 @ 11:07 pm    Reply

    How to Filter a data from database (MySQL) seach by titlename for events application
    I am using in Ionic 2 please send result or Suggestions

    •   deborahk — July 17, 2017 @ 4:41 pm    Reply

      Your best bet is to post to stackoverflow.com. The experts there can help you and often provide sample code.

Trackbacks/Pingbacks

  1. Angular 1.x to Angular 2 Quick Reference -Deborah's Developer MindScape

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2018 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs