26

I have problems with sorting an array of object in Angular2.

The object looks like:

[
  {
    "name": "t10",
    "ts": 1476778297100,
    "value": "32.339264",
    "xid": "DP_049908"
  },
  {
    "name": "t17",
    "ts": 1476778341100,
    "value": "true",
    "xid": "DP_693259"
  },
  {
    "name": "t16",
    "ts": 1476778341100,
    "value": "true",
    "xid": "DP_891890"
  }
]

And is being stored inside the values variable.

All I want is to make the *ngFor loop sort it by the name property.

<table *ngIf="values.length">
    <tr *ngFor="let elem of values">
      <td>{{ elem.name }}</td>
      <td>{{ elem.ts }}</td>
      <td>{{ elem.value }}</td>
    </tr>
</table>

Tried to do it with pipes, but failed miserably. Any help appreciated.

Plunker link: https://plnkr.co/edit/e9laTBnqJKb8VzhHEBmn?p=preview

Edit

My pipe:

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

@Component({
  selector: 'watchlist',
  templateUrl: './watchlist.component.html',
  styleUrls: ['./watchlist.component.css'],
  pipes: [ ArraySortPipe ]
})
@Pipe({
  name: "sort"
})

export class ArraySortPipe implements PipeTransform {
  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 just put the pipe name into html file:

<tr *ngFor="let elem of values | sort">
5
  • what did you try so far? Can you add the pipe you tried to write here? Commented Dec 12, 2016 at 15:09
  • @toskv The pipe which Ive made doesnt work properly but I will add it asap in the edit. Commented Dec 12, 2016 at 15:10
  • thanks. showing your work in important for people to be able to help you. :) Commented Dec 12, 2016 at 15:11
  • Since you are new it might be worth to read the how to ask a guide, if you haven't already. stackoverflow.com/help/how-to-ask Commented Dec 12, 2016 at 15:12
  • maybe you should try sorting on the name property. in the arrow function in the pipe do and a.name < b.name. :) Commented Dec 12, 2016 at 15:18

6 Answers 6

25

Although you can solve this problem with a pipe, you have to ask yourself if the re-usability of a pipe is useful to you in your particular project. Will you often need to sort objects by the "name" key on other arrays or other components in the future? Will this data be changing often enough and in ways that make it hard to simply sort in the component? Will you need the array sorted on any change to the view or inputs?

I created an edited plunker in which the array is sorted in the component's constructor, but there's no reason this functionality couldn't be moved out into its own method (sortValuesArray for instance) for re-use if necessary.

constructor() {
  this.values.sort((a, b) => {
    if (a.name < b.name) return -1;
    else if (a.name > b.name) return 1;
    else return 0;
  });
}

Edited Plunker

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you but it's not exactly what Im looking for.
In some circumstances the code could be even simpler as constructor() { this.values.sort(function(a, b){return a.ts - b.ts}); }
Or constructor() { this.values.sort((a, b) => {return a.ts - b.ts}); }
The argument for this approach is made at angular.io/guide/pipes: "Angular doesn't offer such pipes because they perform poorly ... Filtering and especially sorting are expensive operations. The user experience can degrade severely for even moderate-sized lists when Angular calls these pipe methods many times per second. filter and orderBy have often been abused in AngularJS apps, leading to complaints that Angular itself is slow... The Angular team and many experienced Angular developers strongly recommend moving filtering and sorting logic into the component itself."
24

Try this

Sort from A to end of alpahbet:

this.suppliers.sort((a,b)=>a.SupplierName.localeCompare(b.SupplierName));

Z=>A (reverse order)

this.suppliers.sort((a,b)=>b.SupplierName.localeCompare(a.SupplierName));

4 Comments

Simplest answer that works, doesn't deserve downvotes.
Not down-voting but some explanation of the code would help. A=>Z is this shorthand for an arrow function? Maybe clarify that "localeCompare" is a built-in method for including language-specific characters?
This worked for me when placed inside the constructor where the subscribe is: this.foo.getBar().subscribe(x => { this.thing = x; this.thing= (this.thing || []).sort((a: Thing, b: Thing) => a.name.localeCompare(b.name)); }); I see that the a=>z thing was text. Totally confused for a while there :)
@RinandLen 'localeCompare' returns negative, positive or zero value based on order. Then sort function uses that to sort it. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
13

Your pipe expects strings but it gets objects, you should adapt it:

export class ArraySortPipe implements PipeTransform {
  transform(array: Array<any>): Array<string> {
    array.sort((a: any, b: any) => {
      if (a.name < b.name) {
        return -1;
      } else if (a.name > b.name) {
        return 1;
      } else {
        return 0;
      }
    });
    return array;
  }
}

14 Comments

Unfortunately it doesn't work. After including your solution into my project, I'm receiving an error: Argument of type '{ selector: string; template: any; styles: any[]; pipes: typeof ArraySortPipe[]; }' is not assignable to parameter of type 'Component'. Object literal may only specify known properties, and 'pipes' does not exist in type 'Component'.
It is probably because of the signature, updated it to accept Array<any>, give it a try.
Here's almost the whole component.ts file and the error which appears: jsfiddle.net/ra7szrLn/1 It's still something wrong, even if I've changed everything what you said.
The error is due to compilation issue on jsfiddle or am I missing someting? Why not fork and fix the original plnkr?
This error is from webpack while compiling the app. I've used jsfiddle now because on the plunker Ive posted minimal and very simplified version of my code, without http requests. I can edit the original plunker if you wish, my friend.
|
10

Angular still advice not to use pipes for sorting and filtering, like in angularJs.

It will slow down your application, have some performance issues. It is a lot better to provide your sorting in your component before passing it to the template.

The reason is explained on https://angular.io/guide/pipes#no-filter-pipe

If you work with a nice structured component layout you can do it even on te setter:

 @Input()
  set users(users: Array<User>) {
    this.usersResult = (users || []).sort((a: User, b: User) => a.name < b.name ? -1 : 1)
  }

2 Comments

What is "userResult" (aside from a var you made up)? It's undefined obviously in the code above, not being declared elsewhere, and changing it to any existing value in my component yields "max call stack size exceeded".
User result will be the result of your sorted array. You just reassign it to something else
3

This is adaptable to any such usecase.

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

@Pipe({
  name: 'sortBy'
})
export class SortByPipe implements PipeTransform {
  transform(arr: Array<any>, prop: any, reverse: boolean = false): any {
    if (arr === undefined) return
    const m = reverse ? -1 : 1
    return arr.sort((a: any, b: any): number => {
      const x = a[prop]
      const y = b[prop]
      return (x === y) ? 0 : (x < y) ? -1*m : 1*m
    })
  }
}

Usage:-
<div *ngFor="let item of list | sortBy: 'isDir': true">

UPDATE
Refer Bo's answer as filtering and sorting in pipes is not recommended.

2 Comments

Checkout angular.io/guide/pipes#no-filter-pipe it is not a good practice to sort here.
Bo Vandersteene , Updated my answer. You can cancel your down vote. thanks
-1

its simple, for example if you have this array object:

 cars = ["Dodge", "Fiat", "Audi", "Volvo", "BMW", "Ford"];

and you want to have the object sorted in the HTML front, use:

 <li ng-repeat="x in cars | orderBy">{{x}}</li>

By default, strings are sorted alphabetically, and numbers are sorted numerically.

If you have this array with keys:

customers = [
{"name" : "Bottom-Dollar Marketse" ,"city" : "Tsawassen"},
{"name" : "Alfreds Futterkiste", "city" : "Berlin"},
{"name" : "Bon app", "city" : "Marseille"},
{"name" : "Cactus Comidas para llevar", "city" : "Buenos Aires"},
{"name" : "Bolido Comidas preparadas", "city" : "Madrid"},
{"name" : "Around the Horn", "city" : "London"},
{"name" : "B's Beverages", "city" : "London"}
];

Use:

Sort the array by "city" DESC order:

<li ng-repeat="x in customers | orderBy : '-city'">{{x.name + ", " + x.city}}</li>

Sort the array by "city":

<li ng-repeat="x in customers | orderBy : 'city'">{{x.name + ", " + x.city}}</li>

2 Comments

Obviously ng-repeat is for old versions of angularJs, for recent uses * ngFor
It is still not a good practice, neither in angular 1.x or Angular 2+