78

In Angularjs 1 it is possible to sort and filter the following way:

<ul ng-repeat="friend in friends | filter:query | orderBy: 'name' ">
   <li>{{friend.name}}</li>
</ul>

But I could not find any examples of how to do this in Angularjs 2.0. My question is how to sort and filter in Angularjs 2.0? If it is still not supported, does anyone know when or if it will be put into Angularjs 2.0?

Frederik Struck-Schøning
  • 12,981
  • 8
  • 59
  • 68
alexsoftware
  • 841
  • 1
  • 7
  • 5
  • [These are the only pipes (filters) supported so far](https://github.com/angular/angular/tree/master/modules/angular2/src/core/pipes). [orderBy was removed](https://github.com/angular/angular/pull/2956), but happily you can [implement your own pipes](https://github.com/angular/angular/pull/3572). – Eric Martinez Oct 01 '15 at 16:19
  • Thanks I will try to implement my own pipe for filter and sort. Strange that Angular team decided to remove the feature though. Hope they will put it back so it is simpler to filter and sort. – alexsoftware Oct 03 '15 at 12:15
  • can't you just sort `friends` in your component code? – Red Cricket Jul 05 '18 at 23:08

6 Answers6

83

This isn't added out of the box because the Angular team wants Angular 2 to run minified. OrderBy runs off of reflection which breaks with minification. Check out Miško Heverey's response on the matter.

I've taken the time to create an OrderBy pipe that supports both single and multi-dimensional arrays. It also supports being able to sort on multiple columns of the multi-dimensional array.

<li *ngFor="let person of people | orderBy : ['-lastName', 'age']">{{person.firstName}} {{person.lastName}}, {{person.age}}</li>

This pipe does allow for adding more items to the array after rendering the page, and still sort the arrays with the new items correctly.

I have a write up on the process here.

And here's a working demo: http://fuelinteractive.github.io/fuel-ui/#/pipe/orderby and https://plnkr.co/edit/DHLVc0?p=info

EDIT: Added new demo to http://fuelinteractive.github.io/fuel-ui/#/pipe/orderby

EDIT 2: Updated ngFor to new syntax

Frederik Struck-Schøning
  • 12,981
  • 8
  • 59
  • 68
Cory Shaw
  • 1,130
  • 10
  • 16
  • 2
    Small update. Latest Angular2 *ngFor would use *ngFor="let person of people... instead of the hashtag # – Pat M Jun 28 '16 at 14:16
  • 2
    @CoryShaw great pipe! well done ****clapping emoji***** – Chaos Monkey Sep 07 '16 at 07:47
  • @CoryShaw: I don't see a way to filter by a string. Just a way to sort. Am I missing something? – invot Sep 12 '16 at 20:50
  • 2
    @invot Correct, this pipe is only the orderBy functionality. A second pipe should be used on the array for filtering. select's answer below is a good example for filtering – Cory Shaw Sep 13 '16 at 13:27
  • Holy shit! Really? Isn't a framework supposed to make our lives easier.. – FrancescoMussi Sep 25 '16 at 14:09
  • I find this a little confusing... a collection of objects isn't a mutli-dimensional array its a single dimensional array? – 72GM Jan 19 '17 at 12:28
  • 2
    @72GM multi-dimensional would actually be `[][]`, so you are correct. Would multi-level would be better wording? `[].SomeObjectProperty.SomeProperty` – Cory Shaw Jan 19 '17 at 16:25
  • @CoryShaw thanks for this solution. But this is not working if i want to order by the lastname and then by the firstName. If there are entries like : `{ "lastName": "Schneider", "firstName": "Waldemar" }, { "lastName": "Schneider", "firstName": "Corinna" }` The user Waldemar will be sorted above the user Corinna. Is there any way to handle this? Long story short....if there a equal lastNames and different firstNames it depends on the base order of the array how the result will look like. – envoy Mar 14 '17 at 09:44
  • This answer is now outdated. The github repo has not been touched in almost 2 years and I met lots of resistance when trying to use this with Angular 5. I would suggest creating using the code on one of the other answers as a starting point for a custom pipe. – Dylan Vander Berg Apr 14 '18 at 21:31
  • @DylanVanderBerg it's very outdated, and I can understand the resistance. It is completely open source, so feel free to just copy out the OrderByPipe by itself instead of using the entire Fuel-UI library – Cory Shaw Apr 16 '18 at 15:22
  • 1
    I am using Angular 6 and got it working within about 5 min. Just created a pipe with CLI and then copied the code and fixed all the small things that TS-Lint showed out (just lint errors) and it worked like a charm. Used it 5 times already in my app in the first hour - excellent pipe! – Alfa Bravo Sep 11 '18 at 13:26
24

It is not supported by design. The sortBy pipe can cause real performance issues for a production scale app. This was an issue with angular version 1.

You should not create a custom sort function. Instead, you should sort your array first in the typescript file and then display it. If the order needs to be updated when for example a dropdown is selected then have this dropdown selection trigger a function and call your sort function called from that. This sort function can be extracted to a service so that it can be re-used. This way, the sorting will only be applied when it is required and your app performance will be much better.

trees_are_great
  • 3,881
  • 3
  • 31
  • 62
  • 1
    While I do understand the reasoning behind this, I'll find it quite frustrating to loose such as common feature without an obvious replacement. Sure I can implement my own sort, etc, but why is there no easy replacement which is intended for use in services instead of templates. – schneida Nov 08 '17 at 12:20
  • 1
    @schneida: maybe because there are lots of other libraries that can do sorting for you, in the way you like, and with tradeoffs you know and can live with? compare ie. why `moment` or `moment-timezone` is not included by default, that that's such a common feature without obvious replacement too (btw. see external package `angular2-moment` for corresponding pipes for moment thingies - this also shows how easy it is to do a plugin that will fill the gap) – quetzalcoatl Nov 09 '17 at 14:45
  • Imho sorting and filtering logic belong to your Store selectors (if you use Redux/RxJS or other patterns like that). – Spock Feb 05 '19 at 13:00
18

Here is a simple filter pipe for array of objects that contain attributes with string values (ES6)

filter-array-pipe.js

import {Pipe} from 'angular2/core';

// # Filter Array of Objects
@Pipe({ name: 'filter' })
export class FilterArrayPipe {
  transform(value, args) {
    if (!args[0]) {
      return value;
    } else if (value) {
      return value.filter(item => {
        for (let key in item) {
          if ((typeof item[key] === 'string' || item[key] instanceof String) && 
              (item[key].indexOf(args[0]) !== -1)) {
            return true;
          }
        }
      });
    }
  }
}

Your component

myobjComponent.js

import {Component} from 'angular2/core';
import {HTTP_PROVIDERS, Http} from 'angular2/http';
import {FilterArrayPipe} from 'filter-array-pipe';

@Component({
  templateUrl: 'myobj.list.html',
  providers: [HTTP_PROVIDERS],
  pipes: [FilterArrayPipe]
})
export class MyObjList {
  static get parameters() {
    return [[Http]];
  }
  constructor(_http) {
    _http.get('/api/myobj')
      .map(res => res.json())
      .subscribe(
        data => this.myobjs = data,
        err => this.logError(err))
      );
  }
  resetQuery(){
    this.query = '';
  }
}

In your template

myobj.list.html

<input type="text" [(ngModel)]="query" placeholder="... filter" > 
<div (click)="resetQuery()"> <span class="icon-cross"></span> </div>
</div>
<ul><li *ngFor="#myobj of myobjs| filter:query">...<li></ul>
select
  • 2,513
  • 2
  • 25
  • 36
  • The files look very similar to how the typescript would be written. Are the files actually .ts? – Alex J Mar 11 '16 at 19:36
  • They are not Typepscript, you would have to add types in the function headers `transform(value: ..., args: ...)` and at the variable declarations with `let`. – select Mar 13 '16 at 20:38
  • but when using with node it gives error on filer:query on json object. you know why this error come ? – Jignesh Vagh Apr 19 '17 at 10:09
  • 1
    @JigneshVagh `filter` is an array function, so this will only work on lists of object and not objects itself, hope this helps. – select Apr 24 '17 at 09:21
13

A pipe takes in data as input and transforms it to a desired output. Add this pipe file:orderby.ts inside your /app folder .

orderby.ts

//The pipe class implements the PipeTransform interface's transform method that accepts an input value and an optional array of parameters and returns the transformed value.

import { Pipe,PipeTransform } from "angular2/core";

//We tell Angular that this is a pipe by applying the @Pipe decorator which we import from the core Angular library.

@Pipe({

  //The @Pipe decorator takes an object with a name property whose value is the pipe name that we'll use within a template expression. It must be a valid JavaScript identifier. Our pipe's name is orderby.

  name: "orderby"
})

export class OrderByPipe implements PipeTransform {
  transform(array:Array<any>, args?) {

    // Check if array exists, in this case array contains articles and args is an array that has 1 element : !id

    if(array) {

      // get the first element

      let orderByValue = args[0]
      let byVal = 1

      // check if exclamation point 

      if(orderByValue.charAt(0) == "!") {

        // reverse the array

        byVal = -1
        orderByValue = orderByValue.substring(1)
      }
      console.log("byVal",byVal);
      console.log("orderByValue",orderByValue);

      array.sort((a: any, b: any) => {
        if(a[orderByValue] < b[orderByValue]) {
          return -1*byVal;
        } else if (a[orderByValue] > b[orderByValue]) {
          return 1*byVal;
        } else {
          return 0;
        }
      });
      return array;
    }
    //
  }
}

In your component file (app.component.ts) import the pipe that you just added using: import {OrderByPipe} from './orderby';

Then, add *ngFor="#article of articles | orderby:'id'" inside your template if you want to sort your articles by id in ascending order or orderby:'!id'" in descending order.

We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value

We must list our pipe in the pipes array of the @Component decorator. pipes: [ OrderByPipe ] .

app.component.ts

import {Component, OnInit} from 'angular2/core';
import {OrderByPipe} from './orderby';

@Component({
    selector: 'my-app',
    template: `
      <h2>orderby-pipe by N2B</h2>
      <p *ngFor="#article of articles | orderby:'id'">
        Article title : {{article.title}}
      </p>
    `,
    pipes: [ OrderByPipe ]

})
export class AppComponent{
    articles:Array<any>
    ngOnInit(){
        this.articles = [
        {
            id: 1,
            title: "title1"
        },{
            id: 2,
            title: "title2",
        }]  
    }

}

More info here on my github and this post on my website

Nicolas2bert
  • 1,142
  • 7
  • 10
  • 1
    Please do not post [duplicate answers](//meta.stackexchange.com/a/211726/206345). Instead, consider other actions that could help future users find the answer they need, as described in the linked post. Especially when the post includes links to your website, as those posts start to appear spammy. – Mogsdad Apr 19 '16 at 02:58
  • In that context, please do read [How to offer personal open-source libraries?](https://meta.stackexchange.com/q/229085) too. – Martijn Pieters Apr 19 '16 at 09:01
  • OK guys @Mogsdad - Sorry about that, hope it'll help btw ! – Nicolas2bert Apr 19 '16 at 15:21
  • Please check this issue [link](https://github.com/nicolas2bert/angular2-orderby-pipe/issues/1) – Deepak Apr 28 '16 at 05:21
  • 4
    I liked the way this is structured, easy to understand. Thank you. BUT, update your script please, here AND on you website/github. In first line change "angular2/core" to "@angular/core". Besides this, at start I couldn't get it to work, then I flew through your code and realized your "template" step has a big typo. In template it has to be an array for everything to work as intended: "| orderby:['id']" – Starwave Jul 07 '16 at 11:12
  • I prefer this answer as it is simple and clear (needs a couple of minor syntax changes to TS compliant) – 72GM Jan 19 '17 at 12:58
  • 1
    @Starwave +1 for mentioning the error in the template. Much appreciated. – Gary Apr 09 '17 at 00:34
4

You must create your own Pipe for array sorting, here is one example how can you do that.

<li *ngFor="#item of array | arraySort:'-date'">{{item.name}} {{item.date | date:'medium' }}</li>

https://plnkr.co/edit/DU6pxr?p=preview

Frederik Struck-Schøning
  • 12,981
  • 8
  • 59
  • 68
Vlado Tesanovic
  • 6,369
  • 2
  • 20
  • 31
  • 10
    Why don't they provide this out of the box? It's such a common thing it's ridiculous they don't provide that. – bersling Feb 09 '16 at 21:45
  • Also I think there is a problem in your implementation, it works with '-date' but if you would just write 'date' the `let column = args[0].slice(1);` command makes the column be just `ate` which is obviously not a valid key anymore. – bersling Feb 09 '16 at 22:05
  • 1
    @bersling their reasoning is that they want Angular 2 to be able to be minified and still work correctly. OrderBy requires reflection which isn't able to minified – Cory Shaw Feb 25 '16 at 18:17
  • 2
    That doesn't make sense... all the above solutions can be minified, can't they? How can such a major framework omit such basic functionality... – Kokodoko Nov 27 '16 at 15:36
1

This is my sort. It will do number sort , string sort and date sort .

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

@Pipe({
  name: 'sortPipe'
 })

export class SortPipe implements PipeTransform {

    transform(array: Array<string>, key: string): Array<string> {

        console.log("Entered in pipe*******  "+ key);


        if(key === undefined || key == '' ){
            return array;
        }

        var arr = key.split("-");
        var keyString = arr[0];   // string or column name to sort(name or age or date)
        var sortOrder = arr[1];   // asc or desc order
        var byVal = 1;


        array.sort((a: any, b: any) => {

            if(keyString === 'date' ){

                let left    = Number(new Date(a[keyString]));
                let right   = Number(new Date(b[keyString]));

                return (sortOrder === "asc") ? right - left : left - right;
            }
            else if(keyString === 'name'){

                if(a[keyString] < b[keyString]) {
                    return (sortOrder === "asc" ) ? -1*byVal : 1*byVal;
                } else if (a[keyString] > b[keyString]) {
                    return (sortOrder === "asc" ) ? 1*byVal : -1*byVal;
                } else {
                    return 0;
                }  
            }
            else if(keyString === 'age'){
                return (sortOrder === "asc") ? a[keyString] - b[keyString] : b[keyString] - a[keyString];
            }

        });

        return array;

  }

}
Human Being
  • 8,269
  • 28
  • 93
  • 136