Filtering and Sorting an Array of Objects Using Pipes in Angular

Angular&NodeEnthusiast
The Startup
Published in
6 min readAug 13, 2020

The implementation I want to share is how you could filter an array of non-nested objects using Pipes in Angular.

Consider an array of JSON objects exported from users.ts

export const users=[{“title”:”Monsieur”,”first”:”Niklas”,”last”:”Philippe”,”age”:20,”gender”:”male”,”email”:”niklas.philippe@example.com”},{“title”:”Mrs”,”first”:”Nicoline”,”last”:”Jensen”,”age”:4,”gender”:”female”,”email”:”nicoline.jensen@example.com”},{“title”:”Miss”,”first”:”Lilly”,”last”:”Smith”,”age”:34,”gender”:”female”,”email”:”lily.smith@example.com”},{“title”:”Mr”,”first”:”Julio”,”last”:”Ibanez”,”age”:40,”gender”:”male”,”email”:”julio.ibanez@example.com”},{“title”:”Monsieur”,”first”:”Horst”,”last”:”Bernard”,”age”:52,”gender”:”male”,”email”:”horst.bernard@example.com”}
]

I would like to create a table with all the above details and search for any thing in the table using a search box as well as sort any column in the table in ascending or descending order.

Template:

<input type=”text” [(ngModel)]=”searchTerm” name=”searchTerm” placeholder=”Search”><table class=”table-bordered”>
<tr>
<th>Title</th>
<th>First Name
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-up" id="firstAsc"></i>
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-down sorting" id="firstDesc"></i>
</th>
<th>Last Name
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-up" id="lastAsc"></i>
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-down" id="lastDesc"></i>
</th>
<th>Age
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-up" id="ageAsc"></i>
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-down" id="ageDesc"></i>
</th>
<th>Email
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-up" id="emailAsc"></i>
<i appSortParams (param)="setSortParams($event)" class="fa fa-arrow-down" id="emailDesc"></i>
</th>
</tr><tr *ngFor="let x of users|filter:searchTerm|sort:direction:column:type">
<td>{{x.title}}</td>
<td>{{x.first}}</td>
<td>{{x.last}}</td>
<td>{{x.age}}</td>
<td>{{x.email}}</td>
</tr>
</table>
Search and Sort Table

We have used 2 pipes:filter and sort as you can see below. The sort pipe is chained to the filter pipe. This means the results of the filter pipe are passed as input to the sort pipe.

<tr *ngFor="let x of users|filter:searchTerm|sort:direction:column:type">

We are passing the users array and the searchTerm which the user enters in the search box as arguments to the filter pipe.

The sort pipe takes 4 arguments:The results of the filter pipe, the direction of sorting i.e ascending or descending,the type of column to sort i.e string or numeric and also the column to be sorted.

Component Class:

import {users} from '../users';export class AppComponent {searchTerm:string=””;
direction:string=”asc”;
column:string=”first”;
type:string=”string”;
users=[...users];
setSortParams(param){
this.direction=param.dir;
this.column=param.col;
this.type=param.typ;
}
}

The default value of direction is “asc” and the default value of the column to sort when the component loads is “first”.

Since the “first” column has only string values, the default value of type will be “string”.

First Name column sorted in ascending order

All the <i> tags have an attribute directive appSortParams. The purpose of this directive is to listen for click events and use the ID of the <i> tag clicked to find the column to be sorted,direction of sorting and the type of column to be sorted.

These details need to be returned back to the component.

Lets see how this is achieved.

@Directive({selector: ‘[appSortParams]’})export class SortParamsDirective {
@Output() param:EventEmitter<any>=new EventEmitter();
constructor(private element:ElementRef) { }
@HostListener(‘click’) onClickIcon(){
this.selectSort(this.element.nativeElement.id)
}
selectSort(id){
switch(id){
case “firstAsc”:
this.param.emit({dir:”asc”,col:”first”,typ:”string”})
break;
case “lastAsc”:
this.param.emit({dir:”asc”,col:”last”,typ:”string”})
break;
case “ageAsc”:
this.param.emit({dir:”asc”,col:”age”,typ:”number”})
break;
case “emailAsc”:
this.param.emit({dir:”asc”,col:”email”,typ:”string”})
break;
case “firstDesc”:
this.param.emit({dir:”desc”,col:”first”,typ:”string”})
break;
case “lastDesc”:
this.param.emit({dir:”desc”,col:”last”,typ:”string”})
break;
case “ageDesc”:
this.param.emit({dir:”desc”,col:”age”,typ:”number”})
break;
case “emailDesc”:
this.param.emit({dir:”desc”,col:”email”,typ:”string”})
break;
}}}

Whenever an <i> tag is clicked, the Directive which listens for click events calls the selectSort() passing the ID of the clicked element as argument.

@HostListener(‘click’) onClickIcon(){
this.selectSort(this.element.nativeElement.id)
}

The selectSort() contains a switch statement, we assigns values to the direction,column and type variables based on the ID of the <i> tag clicked.

param is an Output variable which passes information about the direction,column and type via an object. There is an event binding for the param variable and every time the click event triggers, setSortParams() is called in the component.

We are accessing the object in the component via this method in the component.

setSortParams(param){
this.direction=param.dir;
this.column=param.col;
this.type=param.typ;
}

Lets first check the Filtering functionality.

We are creating a Pipe named FilterPipe to help achieve this search.filter is the selector of the FilterPipe.

//filter.ts
@Pipe({
name:’filter’
})
export class FilterPipe implements PipeTransform{transform(items,searchTerm){
let filteredList=[];
if(searchTerm){let newSearchTerm=!isNaN(searchTerm)? searchTerm.toString(): searchTerm.toString().toUpperCase();
let prop;
return items.filter(item=>{
for(let key in item){
prop=isNaN(item[key]) ? item[key].toString().toUpperCase() : item[key].toString();
if(prop.indexOf(newSearchTerm) > -1){
filteredList.push(item);
return filteredList;}
}
})
}
else{
return items;
}}
}

Since the class implements PipeTransform interface, the transform method needs to be defined. This method takes the users list(named as items)and the searchTerm as arguments.

Only if the searchTerm has a non-null value, we shall be performing a search, else we shall return the original users list as it is back to the component.

if(searchTerm){
......Search functionality......
}
else{
return items;
}

Now lets check when there is a non-null searchTerm. We have the below logic.

let newSearchTerm=!isNaN(searchTerm)? searchTerm.toString(): searchTerm.toString().toUpperCase();
let prop;
return items.filter(item=>{
for(let key in item){
prop=isNaN(item[key]) ? item[key].toString().toUpperCase() : item[key].toString();
if(prop.indexOf(newSearchTerm) > -1){
filteredList.push(item);
return filteredList;}
}
})
  1. If the searchTerm is numeric, convert it into a string and if it is non-numeric,convert it into a string and into uppercase.We assign the modified value to a variable newSearchTerm.
let newSearchTerm=
!isNaN(searchTerm)?
searchTerm.toString():
searchTerm.toString().toUpperCase();
  1. Next we are applying the filter method to the users array. The argument item of the filter method will be an object of the array.
  2. We are iterating through each object of the array. key is the object property and item[key] is the property’s value.
for(let key in item){
.......
}

4. Next we are checking if a property’s value is non-numeric. If yes, convert it into string and then uppercase. If not, just convert it into string. We assign the modified property’s value to a block variable prop.

prop=isNaN(item[key]) ? item[key].toString().toUpperCase() : item[key].toString();if(prop.indexOf(newSearchTerm) > -1){
filteredList.push(item);
return filteredList;}
}

We are checking if the prop contains a portion or the entire newsearchTerm using the indexOf operator.

If yes, we add the object item whose property value includes the newsearchTerm to an array filteredList and return it back.

Filtering results based on string “NI”
Filtering results based on number 4

Now lets check the Sorting functionality.

We have created a SortPipe to acheive this. sort is the selector of this pipe.

//sort.ts
@Pipe({name:’sort’})
export class SortPipe implements PipeTransform{transform(items:[],direction:string,column:string,type:string){
let sortedItems=[];
sortedItems=direction===”asc” ?
this.sortAscending(items,column,type):
this.sortDescending(items,column,type)
return sortedItems;
}
sortAscending(items,column,type){
return […items.sort(function(a:any,b:any){
if(type===”string”){
if (a[column].toUpperCase() < b[column].toUpperCase()) return -1;
}
else{
return a[column]-b[column];
}
})]
}
sortDescending(items,column,type){
return […items.sort(function(a:any,b:any){
if(type===”string”){
if (a[column].toUpperCase() > b[column].toUpperCase()) return -1;
}
else{
return b[column]-a[column];
}
})]
}
}

The transform() accepts 4 arguments:results of the filter pipe, the direction, column and type of column.

Based on the direction value, we are calling either sortAscending() or sortDescending().

sortedItems=direction===”asc” ?
this.sortAscending(items,column,type):
this.sortDescending(items,column,type)

In both the methods,the logic for sorting strings and numbers are done separately based on the type value. The column value decides which column needs to be sorted.

sortAscending(items,column,type){
return […items.sort(function(a:any,b:any){
if(type===”string”){
if (a[column].toUpperCase() < b[column].toUpperCase()) return -1;
}
else{
return a[column]-b[column];
}
})]
}
Sorting the age in ascending order
Sorting the Last Name in descending order

You can find the entire working below:

--

--

Angular&NodeEnthusiast
The Startup

Loves Angular and Node. I wish to target issues that front end developers struggle with the most.