in Code, Tech Trends, Tutorials

Creating a Filter Pipe for Angular 2 (in Ionic 2)

Players organized by team in <ion-list>

Players organized by team in <ion-list>

I’ve been getting into the world of building cross-platform mobile apps using the Ionic 2 Framework with Angular 2 for Typescript. Yesterday I needed to build a view that displayed a list of players that were grouped by the team that they’re on. The final view is shown here on the right.

The solution was to create a custom pipe. Pipes are a way to transform data within templates specifically for the purpose of presentation.

Filtering a List of Objects

To start out, the layout displayed a list of Player objects. The raw list looks something like this:

[
    {
        "firstName": "Morgan",
        "lastName": "Benton",
        "username": "mbenton",
        "teamId": 1
    },
    {
        "firstName": "Kelsey",
        "lastName": "Banks",
        "username": "kbanks",
        "teamId": 1
    },
    {
        "firstName": "Jessica",
        "lastName": "Martinez",
        "username": "jmartinez",
        "teamId": 3
    },
    {
        "firstName": "Maggie",
        "lastName": "Walker",
        "username": "mwalker",
        "teamId": 2
    }
]

The template code for displaying this list without filtering would look something like this:

<ion-content>
    <ion-list>
        <ion-item *ngFor="let player of players">
            {{player.firstName}} {{player.lastName}}
        </ion-item>
    </ion-list>
</ion-content>

However, we’d like to group these players by what team they’re on. The list of teams looks like this:

[
    {
        "id": 1,
        "name": "JMU"
    },
    {
        "id": 2,
        "name": "University of Richmond"
    },
    {
        "id": 3,
        "name": "UVA"
    }
]

In order to get what we want, we have to create a custom pipe.

Creating a Custom Pipe

The pipe code looks like this:

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

@Pipe({
    name: "filter",
    pure: false
})
export class ArrayFilterPipe implements PipeTransform {

    transform(items: Array<any>, conditions: {[field: string]: any}): Array<any> {
        return items.filter(item => {
            for (let field in conditions) {
                if (item[field] !== conditions[field]) {
                    return false;
                }
            }
            return true;
        });
    }
}

The first parameter to this pipe is the list of objects to be filtered, e.g. players. The second parameter is an object specifying the filter conditions. Each key in the list of conditions should match a key from the target object type. To use this filter in your view you need to add the pipe to your Component as follows:

import { Component } from "@angular/core";
import { NavController, Loading } from "ionic-angular";
import { Player } from "../../models/player";
import { Team } from "../../models/team";
import { ArrayFilterPipe } from "../../pipes/array-filter.pipe";

@Component({
    templateUrl: "build/pages/players/players.html",
    pipes: [ArrayFilterPipe]
})
export class PlayersPage {

    players: Array<Player>;
    teams: Array<Team>;

    /* ... rest of component implementation ... */
}

Note that this Component has two public properties (players and teams) that are populated in the constructor() so that the lists will be available to the view. The key lines here are line 5, where the filter pipe is imported, and line 9, where we tell the Component that we plan to use this pipe. Once we’ve done this, we can use the pipe in our view like this:

<ion-item-group *ngFor="let team of teams">
    <ion-item-divider>{{team.name}}</ion-item-divider>
    <ion-item *ngFor="let player of players | filter:{teamId:team.id}">
        {{player.firstName}} {{player.lastName}}
    </ion-item>
</ion-item-group>

If you wanted to filter for more than one property value, you could change the value passed to the filter to be something like: {teamId: team.id, firstName: 'Morgan'}, which would return only members of teams with the first name Morgan (I know this is silly, but I hope you see the point).

Improvements?

As you can see, the values that are filtered for can be dynamic, e.g. a team ID that changes as teams are looped through. An improvement might be to update the pipe to allow other types of search expressions, e.g. return players with first names that start with “M”. At any rate, I was very pleased by how clean and elegant this ended up being. Happy filtering!

Write a Comment

Comment

18 Comments

  1. Looks great but would be good to see all of your code such as the constructor setting up the two arrays. Do you have full sample source code available somewhere for further exploration – thanks!

    • I don’t see why not. Just use the value from the first `select` as the input to the filter and trigger it when the `select` changes.

  2. if i want to display instead player id =1 then i have to display skipper if player id=2 then i want to display goal keeper.
    Thanks in advance

    • @vishwanth, I’m not exactly sure what you’re asking. If you have two tables in your database, e.g. `players` and `positions` then you could filter and sort the list based on player positions in the same way that I sorted players by team in the example. This would assume that each player object had a property like `position_id` or something like that.

    • @hardin,

      I’m not sure what you mean. I think you could pretty easily modify this to filter on more than one property of your object. All you would need to do is set your `filter` to be an object with more than one property, e.g. `filter:{prop1:cond1,prop2:cond2,prop3:cond3}`, and then in your `transform()` function just have a more complex algorithm for checking the fields against the filter conditions.

        • I’m not sure what you’re asking. In my example, I used one list to filter another one, but what I think you’re asking would be how can I use the filter if I only have the `players` array and want to display the players on a single team? If you know the `team_id` value that you want to filter for, e.g. 1, then you could have:
          <ion-item *ngFor="let player of players | filter:{teamId:1}">
          {{player.firstName}} {{player.lastName}}
          </ion-item>

    • Hi Asmaa, I’m not sure what you’re asking. If you want to output all of the players at once, you could just remove the filter.

  3. Thank you, great tutorial. Let’s say you would like the filter to return all matching documents where you pass two arguments that matches atleast one of the criterias, would it be possible to implement this with this pipe? Meaning, that the filter should return any doc that matches team OR player in this case? Thanks.

    • Sure. First, in your template:

      Then in the pipe itself, just change the way that the conditions array is handled, e.g. something like: for(let field in conditions){ if(items[field] === conditions[field]) { return true; } return false; }

      That should return any item in the players array that matches any of the criteria in the filter.

  4. Your tutorial is very clear ! I was searching how to group data and the explanation I’ve found about “Pipes” were very confused. Thanks to you, I’ve been able to solve my problem. Thanks a lot for your help.