OrderBy 管道问题

我无法把这段代码从角度1翻译成角度2:

ng-repeat="todo in todos | orderBy: 'completed'"

这就是我根据 Thierry Templer 的回答所做的:

组件模板:

*ngFor="#todo of todos | sort"

组件代码:

@Component({
selector: 'my-app',
templateUrl: "./app/todo-list.component.html",
providers: [TodoService],
pipes: [ TodosSortPipe ]


})

管道编号:

import { Pipe } from "angular2/core";
import {Todo} from './todo';


@Pipe({
name: "sort"
})
export class TodosSortPipe {
transform(array: Array<Todo>, args: string): Array<Todo> {
array.sort((a: any, b: any) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return array;
}
}

我正在尝试排序一个 Todo数组,按属性 completed排序。先是 todo.completed = false,然后是 todo.complete = true

我不太理解 transform方法以及如何在该方法和 sort方法中传递参数。

什么是 args: string论点? 什么是 ab? 它们来自哪里?

291015 次浏览

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

In the current version of Angular2, orderBy and ArraySort pipes are not supported. You need to write/use some custom pipes for this.

I've created an OrderBy pipe that does just what you need. It supports being able to sort on multiple columns of an enumerable of objects as well.

<li *ngFor="#todo in todos | orderBy : ['completed']">\{\{todo.name}} \{\{todo.completed}}</li>

This pipe does allow for adding more items to the array after rendering the page, and will sort the array with the updates dynamically.

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

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."

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;

This will work for any field you pass to it. (IMPORTANT: It will only order alphabetically so if you pass a date it will order it as alphabet not as date)

/*
*      Example use
*      Basic Array of single type: *ngFor="let todo of todoService.todos | orderBy : '-'"
*      Multidimensional Array Sort on single column: *ngFor="let todo of todoService.todos | orderBy : ['-status']"
*      Multidimensional Array Sort on multiple columns: *ngFor="let todo of todoService.todos | orderBy : ['status', '-title']"
*/


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


@Pipe({name: "orderBy", pure: false})
export class OrderByPipe implements PipeTransform {


value: string[] = [];


static _orderByComparator(a: any, b: any): number {


if (a === null || typeof a === "undefined") { a = 0; }
if (b === null || typeof b === "undefined") { b = 0; }


if (
(isNaN(parseFloat(a)) ||
!isFinite(a)) ||
(isNaN(parseFloat(b)) || !isFinite(b))
) {
// Isn"t a number so lowercase the string to properly compare
a = a.toString();
b = b.toString();
if (a.toLowerCase() < b.toLowerCase()) { return -1; }
if (a.toLowerCase() > b.toLowerCase()) { return 1; }
} else {
// Parse strings as numbers to compare properly
if (parseFloat(a) < parseFloat(b)) { return -1; }
if (parseFloat(a) > parseFloat(b)) { return 1; }
}


return 0; // equal each other
}


public transform(input: any, config = "+"): any {
if (!input) { return input; }


// make a copy of the input"s reference
this.value = [...input];
let value = this.value;
if (!Array.isArray(value)) { return value; }


if (!Array.isArray(config) || (Array.isArray(config) && config.length === 1)) {
let propertyToCheck: string = !Array.isArray(config) ? config : config[0];
let desc = propertyToCheck.substr(0, 1) === "-";


// Basic array
if (!propertyToCheck || propertyToCheck === "-" || propertyToCheck === "+") {
return !desc ? value.sort() : value.sort().reverse();
} else {
let property: string = propertyToCheck.substr(0, 1) === "+" || propertyToCheck.substr(0, 1) === "-"
? propertyToCheck.substr(1)
: propertyToCheck;


return value.sort(function(a: any, b: any) {
let aValue = a[property];
let bValue = b[property];


let propertySplit = property.split(".");


if (typeof aValue === "undefined" && typeof bValue === "undefined" && propertySplit.length > 1) {
aValue = a;
bValue = b;
for (let j = 0; j < propertySplit.length; j++) {
aValue = aValue[propertySplit[j]];
bValue = bValue[propertySplit[j]];
}
}


return !desc
? OrderByPipe._orderByComparator(aValue, bValue)
: -OrderByPipe._orderByComparator(aValue, bValue);
});
}
} else {
// Loop over property of the array in order and sort
return value.sort(function(a: any, b: any) {
for (let i = 0; i < config.length; i++) {
let desc = config[i].substr(0, 1) === "-";
let property = config[i].substr(0, 1) === "+" || config[i].substr(0, 1) === "-"
? config[i].substr(1)
: config[i];


let aValue = a[property];
let bValue = b[property];


let propertySplit = property.split(".");


if (typeof aValue === "undefined" && typeof bValue === "undefined" && propertySplit.length > 1) {
aValue = a;
bValue = b;
for (let j = 0; j < propertySplit.length; j++) {
aValue = aValue[propertySplit[j]];
bValue = bValue[propertySplit[j]];
}
}


let comparison = !desc
? OrderByPipe._orderByComparator(aValue, bValue)
: -OrderByPipe._orderByComparator(aValue, bValue);


// Don"t return 0 yet in case of needing to sort by next property
if (comparison !== 0) { return comparison; }
}


return 0; // equal each other
});
}
}
}

Recommend u use lodash with angular, then your pipe will be next:

import {Pipe, PipeTransform} from '@angular/core';
import * as _ from 'lodash'
@Pipe({
name: 'orderBy'
})
export class OrderByPipe implements PipeTransform {


transform(array: Array<any>, args?: any): any {
return _.sortBy(array, [args]);
}


}

and use it in html like

*ngFor = "#todo of todos | orderBy:'completed'"

and don't forget add Pipe to your module

@NgModule({
...,
declarations: [OrderByPipe, ...],
...
})

As we know filter and order by are removed from ANGULAR 2 and we need to write our own, here is a good example on plunker and detailed article

It used both filter as well as orderby, here is the code for order pipe

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({  name: 'orderBy' })
export class OrderrByPipe implements PipeTransform {


transform(records: Array<any>, args?: any): any {
return records.sort(function(a, b){
if(a[args.property] < b[args.property]){
return -1 * args.direction;
}
else if( a[args.property] > b[args.property]){
return 1 * args.direction;
}
else{
return 0;
}
});
};
}

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.

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.

This is good replacement for AngularJs orderby pipe in angular 4. Easy and simple to use.

This is github URL for more information https://github.com/VadimDez/ngx-order-pipe

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


@Pipe({
name: 'orderBy'
})
export class OrderPipe implements PipeTransform {


transform(value: any | any[], expression?: any, reverse?: boolean): any {
if (!value) {
return value;
}


const isArray = value instanceof Array;


if (isArray) {
return this.sortArray(value, expression, reverse);
}


if (typeof value === 'object') {
return this.transformObject(value, expression, reverse);
}


return value;
}


/**
* Sort array
*
* @param value
* @param expression
* @param reverse
* @returns {any[]}
*/
private sortArray(value: any[], expression?: any, reverse?: boolean): any[] {
const isDeepLink = expression && expression.indexOf('.') !== -1;


if (isDeepLink) {
expression = OrderPipe.parseExpression(expression);
}


let array: any[] = value.sort((a: any, b: any): number => {
if (!expression) {
return a > b ? 1 : -1;
}


if (!isDeepLink) {
return a[expression] > b[expression] ? 1 : -1;
}


return OrderPipe.getValue(a, expression) > OrderPipe.getValue(b, expression) ? 1 : -1;
});


if (reverse) {
return array.reverse();
}


return array;
}




/**
* Transform Object
*
* @param value
* @param expression
* @param reverse
* @returns {any[]}
*/
private transformObject(value: any | any[], expression?: any, reverse?: boolean): any {
let parsedExpression = OrderPipe.parseExpression(expression);
let lastPredicate = parsedExpression.pop();
let oldValue = OrderPipe.getValue(value, parsedExpression);


if (!(oldValue instanceof Array)) {
parsedExpression.push(lastPredicate);
lastPredicate = null;
oldValue = OrderPipe.getValue(value, parsedExpression);
}


if (!oldValue) {
return value;
}


const newValue = this.transform(oldValue, lastPredicate, reverse);
OrderPipe.setValue(value, newValue, parsedExpression);
return value;
}


/**
* Parse expression, split into items
* @param expression
* @returns {string[]}
*/
private static parseExpression(expression: string): string[] {
expression = expression.replace(/\[(\w+)\]/g, '.$1');
expression = expression.replace(/^\./, '');
return expression.split('.');
}


/**
* Get value by expression
*
* @param object
* @param expression
* @returns {any}
*/
private static getValue(object: any, expression: string[]) {
for (let i = 0, n = expression.length; i < n; ++i) {
const k = expression[i];
if (!(k in object)) {
return;
}
object = object[k];
}


return object;
}


/**
* Set value by expression
*
* @param object
* @param value
* @param expression
*/
private static setValue(object: any, value: any, expression: string[]) {
let i;
for (i = 0; i < expression.length - 1; i++) {
object = object[expression[i]];
}


object[expression[i]] = value;
}
}

You can use this for objects:

@Pipe({
name: 'sort',
})
export class SortPipe implements PipeTransform {


transform(array: any[], field: string): any[] {
return array.sort((a, b) => a[field].toLowerCase() !== b[field].toLowerCase() ? a[field].toLowerCase() < b[field].toLowerCase() ? -1 : 1 : 0);
}


}

In package.json, add something like (This version is ok for Angular 2):

  "ngx-order-pipe": "^1.1.3",

In your typescript module (and imports array):

  import { OrderModule } from 'ngx-order-pipe';

For Angular 5+ Version we can use ngx-order-pipe package

Source Tutorial Link

Install package

$ npm install ngx-order-pipe --save

Import in apps module

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { OrderModule } from 'ngx-order-pipe';


@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
OrderModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

use anywhere

  <ul>
<li *ngFor="let item of (dummyData | orderBy:'name') ">
\{\{item.name}}
</li>
</ul>
Component template:
todos| sort: ‘property’:’asc|desc’


Pipe code:


import { Pipe,PipeTransform  } from "angular/core";
import {Todo} from './todo';


@Pipe({
name: "sort"
})
export class TodosSortPipe implements PipeTransform {
transform(array: Array<Todo>, args: string): Array<Todo> {
array.sort((a: any, b: any) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {`enter code here`
return 0;
}
});
return array;
}
}

orderby Pipe in Angular JS will support but Angular (higher versions) will not support . Please find he details discussed to increase performance speed its obsolete.

https://angular.io/guide/styleguide#do-not-add-filtering-and-sorting-logic-to-pipes

<!-- const cars=['Audi','Merc','BMW','Volvo','Tesla'] -->


<ul>
<li *ngFor="let car of cars">\{\{car}}</li>
</ul>




/*
*ngFor="let c of oneDimArray | sortBy:'asc'"
*ngFor="let c of arrayOfObjects | sortBy:'asc':'propertyName'"
*/
import { Pipe, PipeTransform } from '@angular/core';
import { orderBy } from 'lodash';


@Pipe({ name: 'sortBy' })
export class SortByPipe implements PipeTransform {


transform(value: any[], order = '', column: string = ''): any[] {
if (!value || order === '' || !order) { return value; } // no array
if (!column || column === '') { return sortBy(value); } // sort 1d array
if (value.length <= 1) { return value; } // array with only one item
return orderBy(value, [column], [order]);
}
}

Adding to Vitali's response, the old documentation about /pipes#appendix-no-filterpipe-or-orderbypipe is no longer available for some reason. It is being discussed here: https://github.com/angular/angular/issues/41652.

Here is @Sal's answer with:

  • Better TypeScript typing
  • Support for arrays of primitives (call without passing in sortKey)
  • input array doesn't get mutated

NOTE: I also removed the Array.isArray() error check.

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


@Pipe({
name: 'sort'
})
export class SortPipe implements PipeTransform {
transform<T, K extends keyof T>(input: T[], sortKey?: K): T[] {
return [...input].sort((a: T, b: T) => {
const aValue = typeof a === 'object'
? a[sortKey]
: a;
const bValue = typeof b === 'object'
? b[sortKey]
: b;


if (aValue < bValue) {
return -1;
} else if (aValue > bValue) {
return 1;
}


return 0;
});
}
}