OrderBy pipe issue
Solution 1:
I modified @Thierry Templier's response so the pipe can sort custom objects in angular 4:
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: "sort"
})
export class ArraySortPipe implements PipeTransform {
transform(array: any, field: string): any[] {
if (!Array.isArray(array)) {
return;
}
array.sort((a: any, b: any) => {
if (a[field] < b[field]) {
return -1;
} else if (a[field] > b[field]) {
return 1;
} else {
return 0;
}
});
return array;
}
}
And to use it:
*ngFor="let myObj of myArr | sort:'fieldName'"
Hopefully this helps someone.
Solution 2:
Please see https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe for the full discussion. This quote is most relevant. Basically, for large scale apps that should be minified aggressively the filtering and sorting logic should move to the component itself.
"Some of us may not care to minify this aggressively. That's our choice. But the Angular product should not prevent someone else from minifying aggressively. Therefore, the Angular team decided that everything shipped in Angular will minify safely.
The Angular team and many experienced Angular developers strongly recommend that you move filtering and sorting logic into the component itself. The component can expose a filteredHeroes or sortedHeroes property and take control over when and how often to execute the supporting logic. Any capabilities that you would have put in a pipe and shared across the app can be written in a filtering/sorting service and injected into the component."
Solution 3:
You could implement a custom pipe for this that leverages the sort
method of arrays:
import { Pipe } from "angular2/core";
@Pipe({
name: "sort"
})
export class ArraySortPipe {
transform(array: Array<string>, args: string): Array<string> {
array.sort((a: any, b: any) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return array;
}
}
And use then this pipe as described below. Don't forget to specify your pipe into the pipes
attribute of the component:
@Component({
(...)
template: `
<li *ngFor="list | sort"> (...) </li>
`,
pipes: [ ArraySortPipe ]
})
(...)
It's a simple sample for arrays with string values but you can have some advanced sorting processing (based on object attributes in the case of object array, based on sorting parameters, ...).
Here is a plunkr for this: https://plnkr.co/edit/WbzqDDOqN1oAhvqMkQRQ?p=preview.
Hope it helps you, Thierry
Solution 4:
Updated OrderByPipe: fixed not sorting strings.
create a OrderByPipe class:
import { Pipe, PipeTransform } from "@angular/core";
@Pipe( {
name: 'orderBy'
} )
export class OrderByPipe implements PipeTransform {
transform( array: Array<any>, orderField: string, orderType: boolean ): Array<string> {
array.sort( ( a: any, b: any ) => {
let ae = a[ orderField ];
let be = b[ orderField ];
if ( ae == undefined && be == undefined ) return 0;
if ( ae == undefined && be != undefined ) return orderType ? 1 : -1;
if ( ae != undefined && be == undefined ) return orderType ? -1 : 1;
if ( ae == be ) return 0;
return orderType ? (ae.toString().toLowerCase() > be.toString().toLowerCase() ? -1 : 1) : (be.toString().toLowerCase() > ae.toString().toLowerCase() ? -1 : 1);
} );
return array;
}
}
in your controller:
@Component({
pipes: [OrderByPipe]
})
or in your
declarations: [OrderByPipe]
in your html:
<tr *ngFor="let obj of objects | orderBy : ObjFieldName: OrderByType">
ObjFieldName: object field name you want to sort;
OrderByType: boolean; true: descending order; false: ascending;
Solution 5:
Angular doesn't come with an orderBy filter out of the box, but if we decide we need one we can easily make one. There are however some caveats we need to be aware of to do with speed and minification. See below.
A simple pipe would look something like this.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'sort'
})
export class SortPipe implements PipeTransform {
transform(ary: any, fn: Function = (a,b) => a > b ? 1 : -1): any {
return ary.sort(fn)
}
}
This pipe accepts a sort function (fn
), and gives it a default value that will sort an array of primitives in a sensible way. We have the option of overriding this sort function if we wish.
It does not accept an attribute name as a string, because attribute names are subject to minification. They will change when we minify our code, but minifiers are not smart enough to also minify the value in the template string.
Sorting primitives (numbers and strings)
We could use this to sort an array of numbers or strings using the default comparator:
import { Component } from '@angular/core';
@Component({
selector: 'cat',
template: `
{{numbers | sort}}
{{strings | sort}}
`
})
export class CatComponent
numbers:Array<number> = [1,7,5,6]
stringsArray<string> = ['cats', 'hats', 'caveats']
}
Sorting an array of objects
If we want to sort an array of objects, we can give it a comparator function.
import { Component } from '@angular/core';
@Component({
selector: 'cat',
template: `
{{cats | sort:byName}}
`
})
export class CatComponent
cats:Array<Cat> = [
{name: "Missy"},
{name: "Squoodles"},
{name: "Madame Pompadomme"}
]
byName(a,b) {
return a.name > b.name ? 1 : -1
}
}
Caveats - pure vs. impure pipes
Angular 2 has a concept of pure and impure pipes.
A pure pipe optimises change detection using object identity. This means that the pipe will only run if the input object changes identity, for example if we add a new item to the array. It will not descent into objects. This means that if we change a nested attribute: this.cats[2].name = "Fluffy"
for example, the pipe will not rerun. This helps Angular to be fast. Angular pipes are pure by default.
An impure pipe on the other hand will check object attributes. This potentially makes it much slower. Because it can't guarantee what the pipe function will do (perhaps it sortd differently based on the time of day for example), an impure pipe will run every time an asynchronous event occurs. This will slow down your app considerably if the array is large.
The pipe above is pure. This means it will only run when the objects in the array are immutable. If you change a cat, you must replace the entire cat object with a new one.
this.cats[2] = {name:"Tomy"}
We can change the above to an impure pipe by setting the pure attribute:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'sort',
pure: false
})
export class SortPipe implements PipeTransform {
transform(ary: any, fn: Function = (a,b) => a > b ? 1 : -1): any {
return ary.sort(fn)
}
}
This pipe will descend into objects, but will be slower. Use with caution.