An Introduction to Angular CLI Part 3 - Sort and filter data with custom pipes

One of first questions those of us who transitioned to Angular from Angular 1 have is, “where’s my order by and filter pipes?” For those of you who don’t know what pipes are, they’re a way to transform and format output in a view. For example, we could use a pipe to format a date to be more readable like this:

<p>{{ unformattedDate | date }}</p>  

When angular interpolates this expression, it will look for and apply a date filter to the unformatted date. We can chain multiple filters by separating them with the pipe operator, and even pass arguments to them.

So now we know the very basics of pipes, let’s talk about some of the built in pipes that angular gives us, and what’s disappeared since the previous version.


Angular gives us a few pipes that we can use in any template, such as date, uppercase, lowercase, currency and percent. But they’ve omitted pipes for filtering and sorting lists that were given to us in the previous version, and there’s a reason why.

Let’s switch to the list application that we’ve been working on for the last 2 parts in this series and create a custom pipe to understand how they work. This will help us understand why we can’t sort and filter our list out of the box, and how we can create a custom pipe using angular cli to get this functionality.

If you haven’t been following along and are interested in creating the list application, head over to the first post before you go any further.


Angular CLI has a built in generator for pipes that can be used exactly how we have done to generate interfaces, services and components. So let’s go ahead and create one that will let push all done items to the bottom, and then sort the done and undone items by their due date. In your terminal, type:

ng g pipe orderByDoneAndDate  

Just like with services, angular cli defaults to generating pipes in the root app folder, but we can change this behavior by specifying a folder in the terminal. It also adds an entry to the app.module so we don’t have to remember to declare it.

The pipe definition that angular cli generates reveals the following key points. Firstly, the pipe class implements the PipeTransform interface and requires a transform method. This method accepts an input value, followed by any parameters. The transform method should return the updated value.

To tell angular that this is a pipe, we apply the @Pipe decorator. The pipe decorator allows us to give the pipe a name that will be used within our template expressions.

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

@Pipe({
  name: 'orderByDoneAndDate'
})
export class OrderByDoneAndDatePipe implements PipeTransform {  
  transform(value: any, args?: any): any {
    return null;
  }
}

Let’s encapsulate the sorting logic in the list service. If we want to update the list logic later, it helps to have it all in one place.

I’m going to separate sorting by done and due date into two separate sort functions. I could do it all in the same function but in this instance i’m favoring readability. If, as the application grows, this becomes a performance bottleneck I can easily come back and update it. An important principle in software development is to embrace iteration. Your code doesn’t need to be perfect the first time, there will always be opportunities to refactor later on.

sort() {  
  this.items
    .sort((a: Item, b: Item) => {
      return a.dueDate.valueOf() - b.dueDate.valueOf();
    })
    .sort((a: Item, b: Item) => {
      return (a.done === b.done ? 0 : (a.done ? 1 : -1));
    });
}

We’re going to use this pipe in the ng for expression that output the list items, so we need to update the transform method to accept our list array. It won’t take and arguments so we can remove the args parameter. Finally the return value will be the sorted list of items.

Let’s set our imports, call the list services sort method, and then return the list items.

import { Pipe, PipeTransform } from '@angular/core';  
import { Item } from './item';  
import { ListService } from './list.service';

@Pipe({
  name: 'orderByDoneAndDate'
})
export class OrderByDoneAndDatePipe implements PipeTransform {  
  constructor(private listService: ListService) {}

  transform(items: Item[]): Item[] {
    this.listService.sort();
    return this.listService.items;
  }
}

[ide - list/list.component.html] Now let’s add the pipe to the ng for to transform the items. This should apply the sort every time the array changes.

*ngFor="let item of list.items | orderByDoneAndDate; let i = index" 

Hmm, there’s no errors but clearly the sort isn’t being applied. There’s a reason for this, and it will explain why the angular team decided not to build this sort functionality into its core library.

The documentation tells us that by default, a pipe only detects change if a primitive input value, such a string or a number is updated, or if an object reference is changed. It also ignores changes within a composite object. This means it won’t call a pipe if you add a value to an array, or update an object property.

The reason for this is performance. Angular tries to avoid doing deep checks for differences. Because our list is an array of objects, angular isn’t watching for changes when something happens.

So now we have two options. Angular let’s us set a pure flag in the pipe’s decorator to override this functionality. When this flag is false, angular executes the pipe during every component change detection cycle. This means it could be called as often as every keystroke or mouse movement. Not ideal when there’s a lot of data, or if we’re creating a complicated transformation, but in some cases it’s unavoidable.

The second, more performant, and therefore preferred option is to avoid using pipes for filtering and sorting altogether. We could just fire the sort method manually when our list array changes from within our service.

Let’s explore both options and see how we could implement them within our list application.


First let’s look at the pipe option. The only change we need to make to our pipe is to add a pure flag to the decorator and set it to false. Let’s see if this works.

@Pipe({
  name: 'orderByDoneAndDate',
  pure: false
})

Cool, now when we toggle the done status, we can see our pipe working to sort the list. But i’m wary that if we have a long list the pipe may be executed too often and effect the user experience. So let’s remove the pipe and call the sort functionality manually.


We want to sort the list every time the “toggleDone” or “addItem” methods are executed. Let’s call the sort method in both of these.

addItem(item: Item) {  
  this.items.push(item);
  this.sort();
  this.save();
}

deleteItem(index: number) {  
  this.items.splice(index, 1);
  this.save();
}

toggleDone(index: number) {  
  this.items[index].done =! this.items[index].done;
  this.sort();
  this.save();
}

We don’t need the pipe in the ng for expression so let’s remove it and test to make sure we haven’t lost any functionality.


Things are still working and I feel a lot more comfortable with this implementation. The list now only resorts itself when something changes, rather than on every cycle.

The lesson we can take from this is that pipes are great for visual transformations on simple values, like formatting a number or currency, but when we’re working with more complex data or transformations, we should probably try and avoid them, and instead defer the logic to the component or service.


In the next part of this series, we’re going update our project to let the user enter their own list, and see how we can save this data to browsers local storage.

If you’ve enjoyed this series so far, hit the like and subscribe buttons below to make sure you don’t miss the next parts.

comments powered by Disqus