Ionic 2 Filter in Templates with Pipes
Angular 1 (Ionic 1) gave us the orderBy
and filter
filters for free. Angular 2 drops them for a couple of reasons. The points outlined in this article cuts both ways for Angular 2 and Ionic 2.
Ionic 2 is just Angular 2 on mobile drugs, there’re’nt any much deviation if any.
One (according to the docs):
“The
filter
andorderBy
have often been abused in Angular 1 apps, leading to complaints that Angular itself is slow. That charge is fair in the indirect sense that Angular 1 prepared this performance trap by offeringfilter
andorderBy
in the first place.”Find more details in the docs: http://devdocs.io/angular~2_typescript/guide/pipes
Fair point. However, now that each developer is supposed to do their ‘own thing’, do you expect Angular to be hailed as fast when each developer concocts something crazy?
Well, that’s just by the way.
Here’s what the end results look like:
Here’s how I’m currently doing filtering of a list. I find this approach close enough to what I would have in Angular 1. I have no idea whether it looks good or bad. For now, it works. I don’t also know if the speeds are going to be hugely affected, as one of the reasons the filterPipe was dropped natively is because of speed.
They say we should just call it Angular, even without the JS?
Duh!
Pipes
Creating pipes in Ionic 2 is similar to Angular 2 if you’re on the command line interfaces: Angular, it is ng
and Ionic, it is ionic
Since this article focuses on Ionic, let’s do it the Ionic way.
In your Ionic project directory,
$ ionic generate pipe Search
Open the newly created search file, and let’s update it with this:
import { Injectable, Pipe } from '@angular/core'; @Pipe({ name: 'search', pure: true }) @Injectable() export class Search { transform(list: any[], searchTerm: string): any[] { if (searchTerm) { searchTerm = searchTerm.toUpperCase(); return list.filter(item => { return item[0].fullname.toUpperCase().indexOf(searchTerm) !== -1 }); } else { return list; } } }
Notes:
We’re using the pure: true
here because we want the filtering to happen as per only an ionInput
event fired.
According to the docs:
Angular executes an impure pipe during every component change detection cycle. An impure pipe will be called a lot, as often as every keystroke or mouse-move.
We don’t want that, an event firing every damn unrelated action by a user. Thus, we will be intercepting the ionInput
event, which fires only when the input value changes.
The
ionInput
refers to: “When an input field value has changed including cleared.”
Remember to declare the pipe above in your app.module.ts
file under the declarations
property array.
Our ListPage
This page is where the list of items are, in which the Pipe we created above will be used within. Let’s say, it is called home.ts
Here’s how mine looks like:
import { Component } from '@angular/core'; import { AlertController, NavController, ModalController } from 'ionic-angular'; import { Backend } from '../../providers/backend'; @Component({ selector: 'page-home', templateUrl: 'home.html', }) export class HomePage { list: any; term: string = ''; constructor( public backend: Backend ) { } ngOnInit() { // this pull items from localstorage via // a simple service. this.list = this.backend.getAll() console.log(this.list); } // this is run whenever the (ionInput) event is fired searchFn(ev: any) { this.term = ev.target.value; } }
Let’s add the contents of the home.html
and see how all fit together now:
<ion-searchbar (ionInput)="searchFn($event)"></ion-searchbar> <ion-list> <ion-item-sliding *ngFor="let item of list | search: term"> <button ion-item (click)="detailItem(item[0])"> <h1>{{ item[0].fullname }}</h1> <h2>{{ item[0].item }}</h2> <h2>Amount Left: {{ item[0].cost - item[0].paid }}</h2> <p>Date: {{ item[0].createdAt }}</p> </button> <ion-item-options> <button ion-button color="light" icon-left (click)="detailItem(item[0])"> <ion-icon name="ios-more"></ion-icon> View </button> <button ion-button color="primary" icon-left (click)="editItem(item[0])"> <ion-icon name="text"></ion-icon> Edit </button> <button ion-button color="danger" icon-left (click)="deleteItem(item[0])"> <ion-icon name="call"></ion-icon> Delete </button> </ion-item-options> </ion-item-sliding> </ion-list>
Now allow the above snippet to soak in.
What is happening:
In English, here’s what is happening.
- A user enters a string into the
<ion-searchbar>
- Since a change happens in the input field, the
(ionInput)
event is trigged, which is plastered to thesearchFn()
function. - The
searchFn()
takes the event as a param, with the value of the triggered event, eventually associated with theterm
. (see thehome.ts
file above) - This
term
variable is bound to the template which is in turn fed into the*ngFor
loop via the term …| search: term
- The custom filter pipe we have above takes 2 arguments: 1) the list we wish to filter, and 2) the term on which we want the 1) filtered.
- If the search term is an empty string, we return the entire list data back to the for loop to iterate over. Otherwise, we do the actual filtering and return the matching values.
- You might have to tweak things a bit to target what part of an object you wish to filter.
Conclusion
The above snippets are a part of an ongoing project of mine and thought of sharing. I plan to use the above approach for more projects in the future.
As you can see on MadeWithFirebase.com, filtering when done on a list with minimal items, they can be very useful.
Should the list grow, simply break them into pages, then filter on each page. Depending on what you’re filtering and what that involves, a max of 50 items of a list per page shouldn’t be much of a challenge for many devices today.
I’m happy to learn better approaches to the above, and if there’s anything wrong, performance-wise or if it is just plain stupid to use the above approach, lemme know in the comments what you use and I’m happy to learn from.
Hope to see you in the next one.