3

How to make a sorting pipe in angular2 with an array of objects

Original Problem:

I have a TODOs list, (Todo[ ]) and I want to sort it every time I make some changes. I want that the completed todo are displayed at the bottom of the list. The Todo object has a property named .completed that stores a boolean value, it will tell us if the todo is completed or not.

Creating the Pipe:

In Angular2 the "OrderBy" pipe does not exist. So we have to build it:

import { Pipe, PipeTransform } from "angular2/core";
//Todo is the interface for our todo object
import {Todo} from './todo';

@Pipe({
  name: "sort",
  //set to false so it will always update, read below the code.
  pure: false
})
export class TodosSortPipe implements PipeTransform {
  transform(array: Todo[], args: any): Todo[] {
    //watch the console to see how many times you pipe is called
    console.log("calling pipe");
    /* javascript is async, so could be that the pipe is called before
    that the todos list is created, in this case we do nothing
    returning the array as it is */
    if (isBlank(array)) return null;
    array.sort((a, b) => {
      if (a.completed < b.completed) {
        return -1;
      //.completed because we want to sort the list by completed property
      } else if (a.completed > b.completed) {
        return 1;
      } else {
        return 0;
      }
    });
    return array;
  }
}

If you didn't understand the sort method check MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

The Pipe is done, let's move to the Component.

Creating the Component:

The AppComponent class, creates an array of Todo, called Todos, getting the objects from a mock with a service.

import {Component, OnInit} from 'angular2/core';
import {Todo} from './todo';
import {TodoService} from './todo.service';
import {TodosSortPipe} from './sort-pipe.component'

@Component({
    //name of the html element
    selector: 'my-app',
    //view of the selector,  " ` " is alt + 9
    templateUrl: "./app/todo-list.component.html",
    providers: [TodoService],
    pipes: [ TodosSortPipe ]
})

export class AppComponent implements OnInit{

    public todos: Todo[];
    public edited = false;
    public changes = 0;
    //creating an istance of todoService
    constructor(private _todoService: TodoService) { };

    //getting the todos list from the service
    getTodos() {
        this._todoService.getTodos().then(todos => this.todos = todos);
    }

   (...)
   editTodo(todo: Todo): void {
        //slice is very important, read below the code
        this.todos = this.todos.slice();
        this.saveTodos();
    }
}

Template implementation

This is the pipe calling:

<li *ngFor="#todo of todos | sort; #i=index">
 (...)
</li>

Demo:

For the full example with all the code: https://plnkr.co/edit/VICRMVNhqdqK9V4rJZYm?p=preview Watch it on github: https://github.com/AndreaMiotto/Angular2-TodoApp

Pipe Unpure

Pipes only change by default when your Pipe input parameters change and not when your data changes. Setting Pure to false, you will make it "unpure" so you pipe will always update.

3 Answers3

1

Perhaps the value todos is null at the beginning because it's loaded asynchronously using HTTP.

To prevent from such use case you could add this in your pipe:

@Pipe({
  name: "sort"
})
export class TodosSortPipe implements PipeTransform {
  transform(array: Todo[], args: any): Todo[] {
    if (array == null) {
      return null;
    }
    (...)
  }
}

Then the value todos will be received and the transform method of the pipe will be called again with this non null value...

Moreover it seems that your <li> tag isn't ended. You must have valid HTML into component templates. I don't know if it's the complete code or a truncated one...

Hope it helps you, Thierry

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Thanks! How the `todos` property is set in your component: `_todoService.getTodos().then((todos) => this.todos = todos)`? – Thierry Templier Feb 03 '16 at 12:38
  • You missed a `>` character at the end of your `li` no? – Thierry Templier Feb 03 '16 at 12:54
  • I updated your plunkr to make it work: plnkr.co/edit/oOTWeZEB46IieLeKpnbp?p=preview. See the use of `slice` on your `todos` array to force the pipe to be called... – Thierry Templier Feb 03 '16 at 13:15
  • Thierry thank you!! but I can't see your changes, what did you do? –  Feb 03 '16 at 13:25
  • You're welcome! See line 58 in file `src/app.component.ts` of the following plunkr https://plnkr.co/edit/oOTWeZEB46IieLeKpnbp?p=preview. I added some comments around... – Thierry Templier Feb 03 '16 at 13:29
  • question edited with the answer with "pipe unpure" solution. –  Feb 03 '16 at 14:30