OrderBy管道问题


97

我无法将此代码从Angualr 1转换为Angular 2:

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

这是我根据蒂埃里·坦佩利(Thierry Templier)的回答所做的:

组件模板:

*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;
  }
}

我试图对Todos 数组进行排序,按属性排序completed。首先todo.completed = false,然后是todo.complete = true

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

有什么args: string说法?它们是什么ab来自哪里?


我在Angular5 +版本freakyjolly.com/中
代码间谍

Answers:


78

我修改了@Thierry Templier的响应,因此管道可以按角度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;
  }
}

并使用它:

*ngFor="let myObj of myArr | sort:'fieldName'"

希望这对某人有帮助。


1
我收到消息:The pipe 'sort' could not be found。我可以以某种方式像在角度2管道中一样在我的组件中注入管道:[ArraySortPipe]吗?
MatijaŽupančić17年

请参阅@Thierry Templier响应,以了解如何将管道注入到您的应用程序组件中
Sal

您需要在模块层次结构声明中包括“ ArraySortPipe”。像这样:从'./../../shared/filters.pipe'导入{ArraySortPipe}; 在“ app.module.ts”及其下的任何模块中。放置:声明:[ArraySortPipe]
Dudi

71

有关完整的讨论,请参见https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe。这句话最相关。基本上,对于应积极缩小规模的大型应用程序,过滤和排序逻辑应移至组件本身。

“我们中的某些人可能并不愿意大胆地缩小尺寸。这是我们的选择。但是Angular产品不应阻止其他人大幅度缩小尺寸。因此,Angular团队决定将Angular中运送的所有物品安全地缩小。

Angular团队和许多经验丰富的Angular开发人员强烈建议您将过滤和排序逻辑移至组件本身。该组件可以公开filteredHeroes或sortedHeroes属性,并控制执行支持逻辑的时间和频率。您将放置在管道中并在应用程序之间共享的任何功能都可以写入过滤/排序服务中,并注入到组件中。”


7
您应该如何以“可以控制何时以及多长时间执行一次支持逻辑”的方式将逻辑“移入组件本身”?有没有很好的例子可以效仿?
Mzzzzzz

1
@Mzzzzzz在提到诸如filteredHeroesand 的属性时sortedHeroes,我想的想法是,在初始化组件时,您需要运行一些排序/过滤逻辑(也许从ngOnInit调用方法),然后使用排序/过滤的结果设置该属性,并仅在存在某些触发条件的情况下才重新运行逻辑/更新属性(例如,用户交互触发AJAX调用以获取更多英雄,或者用户单击复选框以根据某些条件将其中一半过滤掉,等等)
jmq

41

您可以为此实现一个自定义管道,以利用sort数组的方法:

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;
  }
}

然后按如下所述使用此管道。不要忘记pipes在组件的属性中指定管道:

@Component({
  (...)
  template: `
    <li *ngFor="list | sort"> (...) </li>
  `,
  pipes: [ ArraySortPipe ]
})
(...)

这是带有字符串值的数组的简单示例,但是您可以进行一些高级排序处理(对于对象数组,基于对象属性,基于排序参数,...)。

这是为此的一个插件:https ://plnkr.co/edit/WbzqDDOqN1oAhvqMkQRQ?p = preview 。

希望对您有帮助,蒂埃里


1
谢谢您的回答,您能解释一下排序方法吗?

1
实际上,该sort方法是JavaScript Array对象的方法。请参阅此链接:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Thierry Templier

好的,我明白了,它使用带有比较功能的javascript排序方法作为参数。谢谢!

1
不幸的是,弃车者已被弃用。蒂埃里?

4
pipes: [..]声明不再有效(并且不再必要)
phil294

9

更新了OrderByPipe:修复了不对字符串进行排序的问题。

创建一个OrderByPipe类:

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;
  }
}

在您的控制器中:

@Component({
pipes: [OrderByPipe]
})

或在你的

 declarations: [OrderByPipe]

在您的html中:

<tr *ngFor="let obj of objects | orderBy : ObjFieldName: OrderByType">

ObjFieldName:要排序的对象字段名称;

OrderByType:布尔值;true:降序;false:上升;


对于比较a [orderField]-b [orderField]的字符串参数,返回NaN
PiotrPęczek'17

对于日期参数,它不起作用。日期格式为文本将被错误地排序。
拉斐尔·皮佐

9

Angular并没有开箱即用的orderBy过滤器,但是如果我们决定需要一个,我们可以轻松地制造一个。但是,我们需要注意一些注意事项,以确保速度和最小化。见下文。

一个简单的管道看起来像这样。

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)
  }
}

该管道接受排序函数(fn),并为其提供默认值,该默认值将以合理的方式对基元数组进行排序。如果愿意,我们可以选择重写此排序功能。

它不接受属性名称作为字符串,因为属性名称会被缩小。当我们最小化代码时,它们会发生变化,但是最小化器不够聪明,无法同时最小化模板字符串中的值。

排序原语(数字和字符串)

我们可以使用默认的比较器来对数字或字符串数​​组进行排序:

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']
}

排序对象数组

如果要对对象数组进行排序,可以给它一个比较器函数。

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
  }
}

注意事项-纯管道与不纯管道

Angular 2具有纯管道和不纯管道的概念。

纯管道使用对象标识优化更改检测。这意味着管道仅在输入对象更改身份时才运行,例如,如果我们向数组添加新项。它不会下降到物体中。这意味着如果我们更改嵌套属性:this.cats[2].name = "Fluffy"例如,管道将不会重新运行。这有助于Angular更快。默认情况下,角管是纯管道。

另一方面,不纯净的管道将检查对象属性。这可能会使速度变慢。因为它不能保证管道功能会做什么(例如,可能基于一天中的时间排序不同),所以每次异步事件发生时,都会运行不纯管道。如果阵列很大,这将大大降低您的应用程序速度。

上面的管道是纯净的。这意味着它将仅在数组中的对象不可变时运行。如果更换猫,则必须用新的猫对象替换整个猫对象。

this.cats[2] = {name:"Tomy"}

我们可以通过设置pure属性将以上内容更改为不纯管道:

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)
  }
}

该管道将​​下降为对象,但速度会变慢。请谨慎使用。


谢谢..帮助很大。但是有一个问题。如果我们不应该使用管道或过滤器进行排序,最好的方法是什么?我到处搜索,每个人都通过创建管道来提供解决方案。
Pavan Shukla

@PavanShukla您可以使用管道,只需确保数组条目不可变并创建一个纯管道即可。或者,如果没有大数组,则制作不纯的管道并对每个渲染进行排序。或者,创建一个排序数组作为组件的属性,然后进行渲染。
superluminary

我在单击每个cloumn标题时都使用了array.sort逻辑。我正在显示数据阵列上执行此操作。
Pavan Shukla

7

我创建了一个OrderBy管道,它可以满足您的需求。它还支持能够对可枚举的对象的多列进行排序。

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

此管道确实允许在渲染页面后向数组添加更多项,并将动态对数组和更新进行排序。

在这里写下有关该过程的内容

这是一个工作示例:http : //fuelinteractive.github.io/fuel-ui/#/pipe/orderbyhttps://plnkr.co/edit/DHLVc0?p=info


您不处理空值。
阿里·哈比布扎德

如果(a == null)a = 0; 如果(b == null)b = 0;
阿里·哈比布扎德

当您在界面中单击时,具有相等值的值也会跳动并移动
Ali Habibzadeh

@XGreen对此表示感谢。我将在下一个fuel-ui更新中添加对null / undefined值的支持。至于相等值的跳跃性,我没有看到。你使用的是什么浏览器?
科里·肖

Chrome版本50.0.2661.86(64位),OSX El Capitan
Ali Habibzadeh,2016年

4

推荐您使用带有角的lodash,然后您的管道将是下一个:

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]);
    }

}

并像html一样使用它

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

并且不要忘记将Pipe添加到您的模块中

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

我喜欢您的方法АлександрПетрик,但我更喜欢在模板上发送数组:orderBy:['field1','field2']然后在管道上调用:return _.sortBy(array,args);
艾瑞克(Eric)

1
使用_.sortBy的问题是您无法指定后代顺序。我发现使用_.orderBy您可以为每个字段指定自定义顺序。即:_.orderBy(array,['field1','field2'],['asc','desc'])
Eric

3

这将适用于您传递给它的任何字段。(重要提示:它将仅按字母顺序排序,因此,如果您传递日期,则将其按字母顺序而不是日期排序)

/*
 *      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
            });
        }
    }
}

您可以张贴用法示例吗?
TheUnreal

我无法编译您提供的代码。我收到一个错误消息,说它@Component没有pipes属性。
方位角

3

这是Angular 4中 AngularJs orderby管道的良好替代品。易于使用。

这是github URL以获取更多信息 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;
  }
}

2

如我们所知,过滤器和排序依据已从ANGULAR 2中删除,我们需要编写自己的过滤器和排序依据,这是关于plunker详细文章的

它同时使用过滤器和orderby,这是订单管道的代码

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;
          }
        });
    };
 }

2

您可以将其用于对象:

@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);
  }

}

2

在package.json中,添加类似(Angular 2可以使用此版本):

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

在您的打字稿模块(和导入数组)中:

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

1
<!-- 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]);
  }
}

1
谢谢,很好的回答
AM-EVS

0

在当前版本的Angular2中,不支持orderBy和ArraySort管道。您需要为此编写/使用一些自定义管道。


0

对于Angular 5+版本,我们可以使用ngx-order-pipe软件包

源教程链接

安装套件

$ npm install ngx-order-pipe --save

导入应用程序模块

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 { }

在任何地方使用

  <ul>
    <li *ngFor="let item of (dummyData | orderBy:'name') ">
      {{item.name}}
    </li>
  </ul>


-1
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;
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.