Sort an array of objects in Angular2

84,669

Solution 1

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

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

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.

Share:
84,669

Related videos on Youtube

Admin
Author by

Admin

Updated on July 09, 2022

Comments

  • Admin
    Admin almost 2 years

    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">
    
    • toskv
      toskv over 7 years
      what did you try so far? Can you add the pipe you tried to write here?
    • Admin
      Admin over 7 years
      @toskv The pipe which Ive made doesnt work properly but I will add it asap in the edit.
    • toskv
      toskv over 7 years
      thanks. showing your work in important for people to be able to help you. :)
    • toskv
      toskv over 7 years
      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
    • toskv
      toskv over 7 years
      maybe you should try sorting on the name property. in the arrow function in the pipe do and a.name < b.name. :)
  • Admin
    Admin over 7 years
    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'.
  • Meir
    Meir over 7 years
    It is probably because of the signature, updated it to accept Array<any>, give it a try.
  • Admin
    Admin over 7 years
    Thank you but it's not exactly what Im looking for.
  • Admin
    Admin over 7 years
    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.
  • Meir
    Meir over 7 years
    The error is due to compilation issue on jsfiddle or am I missing someting? Why not fork and fix the original plnkr?
  • Admin
    Admin over 7 years
    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.
  • Meir
    Meir over 7 years
    What version on angular are you using? Is it with angular-cli? The pipes keyword was removed in the final release and you should import the pipe in the module instead
  • Admin
    Admin over 7 years
    Yes, I'm using the angular-cli. Version: webpack 2.1.0-beta.25. I didn't know about the obligatory to use it in the module. Thank you for that.
  • Meir
    Meir over 7 years
    Best way is the you use 'ng g pipe my-pipe' and see how angular adds it and if it creates a module
  • Admin
    Admin over 7 years
    Angular-cli created only two files, one for tests and another is a component, with following code: import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'sortBy' }) export class SortByPipe implements PipeTransform { transform(value: any, args?: any): any { return null; } }
  • Meir
    Meir over 7 years
    Try putting the logic inside this one
  • Admin
    Admin over 7 years
    Uh, hold on, it has also added this code into the app.module: import { SortByPipe } from './sort-by.pipe';
  • Meir
    Meir over 7 years
    That's what I said. No pipes field, just import in the module
  • Admin
    Admin over 7 years
    But I dont fully understand, you mean that I have to delete the @Pipe and the pipe class from the component, and just use that file created by angular?
  • Mickey Segal
    Mickey Segal almost 7 years
    In some circumstances the code could be even simpler as constructor() { this.values.sort(function(a, b){return a.ts - b.ts}); }
  • Mickey Segal
    Mickey Segal almost 7 years
    As discussed at angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe using the pipe approach is discouraged because "The user experience can degrade severely for even moderate-sized lists when Angular calls these pipe methods many times per second... The Angular team and many experienced Angular developers strongly recommend moving filtering and sorting logic into the component itself." This approach was used in the answer by @Peter.
  • Mickey Segal
    Mickey Segal almost 7 years
    Or constructor() { this.values.sort((a, b) => {return a.ts - b.ts}); }
  • Mickey Segal
    Mickey Segal almost 7 years
    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."
  • Wanjia
    Wanjia over 6 years
    Simplest answer that works, doesn't deserve downvotes.
  • Andres Pirona
    Andres Pirona almost 6 years
    Obviously ng-repeat is for old versions of angularJs, for recent uses * ngFor
  • Rin and Len
    Rin and Len almost 6 years
    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?
  • Rin and Len
    Rin and Len almost 6 years
    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".
  • Rin and Len
    Rin and Len almost 6 years
    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 :)
  • Bo Vandersteene
    Bo Vandersteene over 5 years
    User result will be the result of your sorted array. You just reassign it to something else
  • Bo Vandersteene
    Bo Vandersteene over 5 years
    Checkout angular.io/guide/pipes#no-filter-pipe it is not a good practice to sort here.
  • Bo Vandersteene
    Bo Vandersteene over 5 years
    It is still not a good practice, neither in angular 1.x or Angular 2+
  • Vikas Gautam
    Vikas Gautam over 5 years
    Bo Vandersteene , Updated my answer. You can cancel your down vote. thanks
  • Devaarth
    Devaarth over 3 years
    @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/…