68

I have four table headers:

$scope.headers = ["Header1", "Header2", "Header3", "Header4"];

And I want to be able to sort my table by clicking on the header.

So if my table looks like this

H1 | H2 | H3 | H4
A    H    D   etc....
B    G    C
C    F    B
D    E    A

and I click on

H2

my table now looks like this:

H1 | H2 | H3 | H4
D    E    A   etc....
C    F    B
B    G    C
A    H    D

That is, the content of each column never changes, but by clicking on the header I want to order the columns by, the rows will reorder themselves.

The content of my table is created by a database call done with Mojolicious and is returned to the browser with

$scope.results = angular.fromJson(data); // This works for me so far

The rest of the code I have cobbled together looks something like this:

<table class= "table table-striped table-hover">
    <th ng-repeat= "header in headers">
        <a> {{headers[$index]}} </a>
    </th>
    <tr ng-repeat "result in results">
        <td> {{results.h1}} </td>
        <td> {{results.h2}} </td>
        <td> {{results.h3}} </td>
        <td> {{results.h4}} </td>
    </tr>
</table>

How do I order the columns from this point, just by clicking on the header at the top of the table?

4
  • 3
    Why don't you do <a> {{header}} </a> instead of <a> {{headers[$index]}} </a> ? Commented Oct 1, 2013 at 18:15
  • 1
    Both worked. Idk, it seemed more explicit which header was being thrown down. Commented Oct 1, 2013 at 18:16
  • 1
    I think the way I wrote it is more "Best practice" Commented Oct 1, 2013 at 18:21
  • I agree with you. But whatever the OP decides, it ought to be consistent in both heaers and rows, which it currently is not. Commented Mar 23, 2017 at 12:52

9 Answers 9

174

I think this working CodePen example that I created will show you exactly how to do what you want.

The template:

<section ng-app="app" ng-controller="MainCtrl">
  <span class="label">Ordered By: {{orderByField}}, Reverse Sort: {{reverseSort}}</span><br><br>
  <table class="table table-bordered">
    <thead>
      <tr>
        <th>
          <a href="#" ng-click="orderByField='firstName'; reverseSort = !reverseSort">
          First Name <span ng-show="orderByField == 'firstName'"><span ng-show="!reverseSort">^</span><span ng-show="reverseSort">v</span></span>
          </a>
        </th>
        <th>
          <a href="#" ng-click="orderByField='lastName'; reverseSort = !reverseSort">
            Last Name <span ng-show="orderByField == 'lastName'"><span ng-show="!reverseSort">^</span><span ng-show="reverseSort">v</span></span>
          </a>
        </th>
        <th>
          <a href="#" ng-click="orderByField='age'; reverseSort = !reverseSort">
          Age <span ng-show="orderByField == 'age'"><span ng-show="!reverseSort">^</span><span ng-show="reverseSort">v</span></span>
          </a>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="emp in data.employees|orderBy:orderByField:reverseSort">
        <td>{{emp.firstName}}</td>
        <td>{{emp.lastName}}</td>
        <td>{{emp.age}}</td>
      </tr>
    </tbody>
  </table>
</section>

The JavaScript code:

var app = angular.module('app', []);

app.controller('MainCtrl', function($scope) {
  $scope.orderByField = 'firstName';
  $scope.reverseSort = false;

  $scope.data = {
    employees: [{
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    },{
      firstName: 'Frank',
      lastName: 'Burns',
      age: 54
    },{
      firstName: 'Sue',
      lastName: 'Banter',
      age: 21
    }]
  };
});
Sign up to request clarification or add additional context in comments.

7 Comments

This is the closest I got it to working. However, I have some hidden headers, they are only unhidden by opening up a modal and selecting a checkbox (the idea being that you shouldn't have to look at columns that aren't relevant to what you need) The sorting works great for the 4 required columns, but then when I open the modal to select additional headers/columns to display, whenever I next try to do a search, the page reloads..?
@Zack - Is this example what you are talking about?
@Zack - If you feel my answer and comment provided a working solution for your original question please accept it so others looking for the solution will easily find it. Thanks.
Thanks, no it was not your example but some further things in my code that I did not feel the need to write to get my point across. Those issues have since been fixed and your answer accepted :)
Thanks so much, I've been looking around for a solution that worked for Angular 1.2
|
5

Here is a fiddle that can help you to do this with AngularJS
http://jsfiddle.net/patxy/D2FsZ/

<th ng:repeat="(i,th) in head" ng:class="selectedCls(i)" ng:click="changeSorting(i)">
     {{th}}
</th>

Then something like this for your data:

<tr ng:repeat="row in body.$orderBy(sort.column, sort.descending)">
    <td>{{row.a}}</td>
    <td>{{row.b}}</td>
    <td>{{row.c}}</td>
</tr>

With such functions in your AngularJS controller:

scope.sort = {
    column: 'b',
    descending: false
};

scope.selectedCls = function(column) {
    return column == scope.sort.column && 'sort-' + scope.sort.descending;
};

scope.changeSorting = function(column) {
    var sort = scope.sort;
    if (sort.column == column) {
        sort.descending = !sort.descending;
    } else {
        sort.column = column;
        sort.descending = false;
    }
};

3 Comments

this fiddle is using a very old version of angular, so please be aware of that when looking at the code.
the answer is from 2013, the fiddle is from 2010, the fiddle uses a pre 1.0 version. they are now at 1.2/1.3
this is my fiddle... the fiddle is from 2013. In 2010 I did not know anything about programming...
2

Another way to do this in AngularJS is to use a Grid.

The advantage with grids is that the row sorting behavior you are looking for is included by default.

The functionality is well encapsulated. You don't need to add ng-click attributes, or use scope variables to maintain state:

    <body ng-controller="MyCtrl">
        <div class="gridStyle" ng-grid="gridOptions"></div>
    </body>

You just add the grid options to your controller:

  $scope.gridOptions = {
    data: 'myData.employees',
    columnDefs: [{
      field: 'firstName',
      displayName: 'First Name'
    }, {
      field: 'lastName',
      displayName: 'Last Name'
    }, {
      field: 'age',
      displayName: 'Age'
    }]
  };

Full working snippet attached:

var app = angular.module('myApp', ['ngGrid', 'ngAnimate']);
app.controller('MyCtrl', function($scope) {

  $scope.myData = {
    employees: [{
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    }, {
      firstName: 'Frank',
      lastName: 'Burns',
      age: 54
    }, {
      firstName: 'Sue',
      lastName: 'Banter',
      age: 21
    }]
  };

  $scope.gridOptions = {
    data: 'myData.employees',
    columnDefs: [{
      field: 'firstName',
      displayName: 'First Name'
    }, {
      field: 'lastName',
      displayName: 'Last Name'
    }, {
      field: 'age',
      displayName: 'Age'
    }]
  };
});
/*style.css*/
.gridStyle {
    border: 1px solid rgb(212,212,212);
    width: 400px;
    height: 200px
}
<!DOCTYPE html>
<html ng-app="myApp">
    <head lang="en">
        <meta charset="utf-8">
        <title>Custom Plunker</title>
        <link rel="stylesheet" type="text/css" href="http://angular-ui.github.com/ng-grid/css/ng-grid.css" />
        <link rel="stylesheet" type="text/css" href="style.css" />
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular-animate.js"></script>
        <script type="text/javascript" src="http://angular-ui.github.com/ng-grid/lib/ng-grid.debug.js"></script>
        <script type="text/javascript" src="main.js"></script>
    </head>
    <body ng-controller="MyCtrl">
        <div class="gridStyle" ng-grid="gridOptions"></div>
    </body>
</html>

2 Comments

Nice alternative. Too bad it's dependent on jQuery.
The newest ui-grid no longer has a dependence on jQuery. It's still unstable but worth checking out: ui-grid.info
1

Here is an example that sorts by the header. This table is dynamic and changes with the JSON size.

I was able to build a dynamic table off of some other people's examples and documentation. http://jsfiddle.net/lastlink/v7pszemn/1/

<tr>
    <th class="{{header}}" ng-repeat="(header, value) in items[0]" ng-click="changeSorting(header)">
    {{header}}
  <i ng-class="selectedCls2(header)"></i>
</tr>

<tbody>
    <tr ng-repeat="row in pagedItems[currentPage] | orderBy:sort.sortingOrder:sort.reverse">
        <td ng-repeat="cell in row">
              {{cell}}
       </td>
    </tr>

Although the columns are out of order, on my .NET project they are in order.

Comments

1

You can use this code without arrows.....i.e by clicking on header it automatically shows ascending and descending order of elements

    <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="scripts/angular.min.js"></script>
    <script src="Scripts/Script.js"></script>
    <style>
        table {
            border-collapse: collapse;
            font-family: Arial;
        }

        td {
            border: 1px solid black;
            padding: 5px;
        }

        th {
            border: 1px solid black;
            padding: 5px;
            text-align: left;
        }
    </style>
</head>
<body ng-app="myModule">
    <div ng-controller="myController">

        <br /><br />
        <table>
            <thead>
                <tr>
                    <th>
                        <a href="#" ng-click="orderByField='name'; reverseSort = !reverseSort">
                            Name
                        </a>
                    </th>
                    <th>
                        <a href="#" ng-click="orderByField='dateOfBirth'; reverseSort = !reverseSort">
                            Date Of Birth
                        </a>
                    </th>
                    <th>
                        <a href="#" ng-click="orderByField='gender'; reverseSort = !reverseSort">
                           Gender
                        </a>
                    </th>
                    <th>
                        <a href="#" ng-click="orderByField='salary'; reverseSort = !reverseSort">
                            Salary
                        </a>
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="employee in employees | orderBy:orderByField:reverseSort">
                    <td>
                        {{ employee.name }}
                    </td>
                    <td>
                        {{ employee.dateOfBirth | date:"dd/MM/yyyy" }}
                    </td>
                    <td>
                        {{ employee.gender }}
                    </td>
                    <td>
                        {{ employee.salary  }}
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <script>
        var app = angular
        .module("myModule", [])
        .controller("myController", function ($scope) {

            var employees = [
                {
                    name: "Ben", dateOfBirth: new Date("November 23, 1980"),
                    gender: "Male", salary: 55000
                },
                {
                    name: "Sara", dateOfBirth: new Date("May 05, 1970"),
                    gender: "Female", salary: 68000
                },
                {
                    name: "Mark", dateOfBirth: new Date("August 15, 1974"),
                    gender: "Male", salary: 57000
                },
                {
                    name: "Pam", dateOfBirth: new Date("October 27, 1979"),
                    gender: "Female", salary: 53000
                },
                {
                    name: "Todd", dateOfBirth: new Date("December 30, 1983"),
                    gender: "Male", salary: 60000
                }
            ];

            $scope.employees = employees;
            $scope.orderByField = 'name';
            $scope.reverseSort = false;

        });
    </script>
</body>
</html>

Comments

0

Use a third-party JavaScript library. It will give you that and much more. A good example is datatables (if you are also using jQuery): https://datatables.net

Or just order your bound array in $scope.results when the header is clicked.

5 Comments

this should still allow me to use the Json object in retrieving the data from the DB? This looks good bu I sort of need it to work with what I ahve so far. It plays nice with Angular JS?
I don't see why not, searching the web someone already built a directive you can use (though take a look at the code to see if it's written ok): groups.google.com/forum/m/#!topic/angular/vM2DEMK_NMA
Alright, I'm going to backup what I have so far and try implenting this, it looks cool enough. Thanks :D
You can also just order your array in $scope.results , but it's a waste of time to make a good UX for that. Plus I gurantee you will need more advanced stuff later that already exists in datatables :) good luck!
How can I order my array like you said? I will go back and investigate later, but right no, I just need the columns to be sortable for tomorrow
0

I'm just getting my feet wet with angular, but I found this great tutorial.
Here's a working plunk I put together with credit to Scott Allen and the above tutorial. Click search to display the sortable table.

For each column header you need to make it clickable - ng-click on a link will work. This will set the sortName of the column to sort.

<th>
     <a href="#" ng-click="sortName='name'; sortReverse = !sortReverse">
          <span ng-show="sortName == 'name' && sortReverse" class="glyphicon glyphicon-triangle-bottom"></span>
          <span ng-show="sortName == 'name' && !sortReverse" class="glyphicon glyphicon-triangle-top"></span>
           Name
     </a>
</th>

Then, in the table body you can pipe in that sortName in the orderBy filter orderBy:sortName:sortReverse

<tr ng-repeat="repo in repos | orderBy:sortName:sortReverse | filter:searchRepos">
     <td>{{repo.name}}</td>
     <td class="tag tag-primary">{{repo.stargazers_count | number}}</td>
     <td>{{repo.language}}</td>
</tr>

Comments

0

I had found the easiest way to solve this question. If efficient you can use

HTML code: import angular.min.js and the angular.route.js library

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>like/dislike</title>
</head>
<body ng-app="myapp" ng-controller="likedislikecntrl" bgcolor="#9acd32">
<script src="./modules/angular.min.js"></script>
<script src="./modules/angular-route.js"></script>
<script src="./likedislikecntrl.js"></script>
</select></h1></p>
<table border="5" align="center">
<thead>
<th>professorname <select ng-model="sort1" style="background-color: 
chartreuse">
<option value="+name" >asc</option>
<option value="-name" >desc</option>
</select></th>
<th >Subject <select ng-model="sort1">
<option value="+subject" >asc</option>
<option value="-subject" >desc</option></select></th>
<th >Gender <select ng-model="sort1">
<option value="+gender">asc</option>
<option value="-gender">desc</option></select></th>
<th >Likes <select ng-model="sort1">
<option value="+likes" >asc</option>
<option value="-likes" >desc</option></select></th>
<th >Dislikes <select ng-model="sort1">
<option value="+dislikes" >asc</option>
<option value="-dislikes">desc</option></select></th>
<th rowspan="2">Like/Dislike</th>
</thead>
<tbody>
<tr ng-repeat="sir in sirs | orderBy:sort1|orderBy:sort|limitTo:row" >
<td >{{sir.name}}</td>
<td>{{sir.subject|uppercase}}</td>
<td>{{sir.gender|lowercase}}</td>
<td>{{sir.likes}}</td>
<td>{{sir.dislikes}}</td>
<td><button ng-click="ldfi1(sir)" style="background-color:chartreuse" 
>Like</button></td>
<td><button ng-click="ldfd1(sir)" style="background-
color:chartreuse">Dislike</button></td>
</tr>
</tbody>
</table>
</body> 
</html>
JavaScript Code::likedislikecntrl.js

var app=angular.module("myapp",["ngRoute"]);

app.controller("likedislikecntrl",function ($scope) {
var sirs=[
    {name:"Srinivas",subject:"dmdw",gender:"male",likes:0,dislikes:0},
    {name:"Sharif",subject:"dms",gender:"male",likes:0,dislikes:0},
    {name:"Chaitanya",subject:"daa",gender:"male",likes:0,dislikes:0},
    {name:"Pranav",subject:"wt",gender:"male",likes:0,dislikes:0},
    {name:"Anil Chowdary",subject:"ds",gender:"male",likes:0,dislikes:0},
    {name:"Rajesh",subject:"mp",gender:"male",likes:0,dislikes:0},
    {name:"Deepak",subject:"dld",gender:"male",likes:0,dislikes:0},
    {name:"JP",subject:"mp",gender:"male",likes:0,dislikes:0},
    {name:"NagaDeepthi",subject:"oose",gender:"female",likes:0,dislikes:0},
    {name:"Swathi",subject:"ca",gender:"female",likes:0,dislikes:0},
    {name:"Madavilatha",subject:"cn",gender:"female",likes:0,dislikes:0}



]
$scope.sirs=sirs;
$scope.ldfi1=function (sir) {
    sir.likes++

}
$scope.ldfd1=function (sir) {
   sir.dislikes++

}
$scope.row=8;

})

Comments

0

Try this:

First change your controller

    yourModuleName.controller("yourControllerName", function ($scope) {
        var list = [
            { H1:'A', H2:'B', H3:'C', H4:'d' },
            { H1:'E', H2:'B', H3:'F', H4:'G' },
            { H1:'C', H2:'H', H3:'L', H4:'M' },
            { H1:'I', H2:'B', H3:'E', H4:'A' }
        ];

        $scope.list = list;

        $scope.headers = ["Header1", "Header2", "Header3", "Header4"];

        $scope.sortColumn = 'Header1';

        $scope.reverseSort = false;

        $scope.sortData = function (columnIndex) {
            $scope.reverseSort = ($scope.sortColumn == $scope.headers[columnIndex]) ? !$scope.reverseSort : false;

            $scope.sortColumn = $scope.headers[columnIndex];
        }

    });

then change code in html side like this

    <th ng-repeat= "header in headers">
            <a ng-click="sortData($index)"> {{headers[$index]}} </a>
    </th>
    <tr ng-repeat "result in results | orderBy : sortColumn : reverseSort">
        <td> {{results.h1}} </td>
        <td> {{results.h2}} </td>
        <td> {{results.h3}} </td>
        <td> {{results.h4}} </td>
    </tr>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.