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!
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!
Here’s a link to the repo for the project that uses this filter. That being said, the filter just assumes that you have two arrays in JSON format of objects, where one array is to be grouped by the id’s of objects in the other.
Could this be used to filter the options in a select based on another select?
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.
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
in my table view.
@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.
see everyone does a single filter, how can I build with three filters
@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.
How to hide a group if it has no child elements?
@Maarten, you might be able to do this with CSS. Have you checked out the `:empty` pseudo-selector? I’m thinking there might be a clever way to do something in CSS like `ion-list:empty { display: none; }`
and what if you have one object? the team id is in the players object?
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>
this is very useful,
but how i return the full array at the beginning of the start app?
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.
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.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.