133

I'm trying to add pages to my list. I followed the AngularJS tutorial, the one about smartphones and I'm trying to display only certain number of objects. Here is my html file:

  <div class='container-fluid'>
    <div class='row-fluid'>
        <div class='span2'>
            Search: <input ng-model='searchBar'>
            Sort by: 
            <select ng-model='orderProp'>
                <option value='name'>Alphabetical</option>
                <option value='age'>Newest</option>
            </select>
            You selected the phones to be ordered by: {{orderProp}}
        </div>

        <div class='span10'>
          <select ng-model='limit'>
            <option value='5'>Show 5 per page</option>
            <option value='10'>Show 10 per page</option>
            <option value='15'>Show 15 per page</option>
            <option value='20'>Show 20 per page</option>
          </select>
          <ul class='phones'>
            <li class='thumbnail' ng-repeat='phone in phones | filter:searchBar | orderBy:orderProp | limitTo:limit'>
                <a href='#/phones/{{phone.id}}' class='thumb'><img ng-src='{{phone.imageUrl}}'></a>
                <a href='#/phones/{{phone.id}}'>{{phone.name}}</a>
                <p>{{phone.snippet}}</p>
            </li>
          </ul>
        </div>
    </div>
  </div>

I've added a select tag with some values in order to limit the number of items that will be displayed. What I want now is to add the pagination to display the next 5, 10, etc.

I have a controller that works with this:

function PhoneListCtrl($scope, Phone){
    $scope.phones = Phone.query();
    $scope.orderProp = 'age';
    $scope.limit = 5;
}

And also I have a module in order to retrieve the data from the json files.

angular.module('phonecatServices', ['ngResource']).
    factory('Phone', function($resource){
        return $resource('phones/:phoneId.json', {}, {
            query: {method: 'GET', params:{phoneId:'phones'}, isArray:true}
        });
    });
Vertexwahn
  • 7,709
  • 6
  • 64
  • 90
Tomarto
  • 2,755
  • 6
  • 27
  • 37
  • 1
    When you say you want to implement next page and previous page, do you want the pagination to happen purely on client side or on server side. If the number of records are too high then you should opt for server side pagination. Under any scenario you need to start maintaining "startIndex" - limit would only provide number of records on page, apart from this you need to some how maintain current page - this can be done by maintaining startIndex. – Rutesh Makhijani Jul 20 '12 at 14:25
  • I don't have a high number of records. What I wanted to do is to use the controller I already have (PhoneListCtrl). I don't know if it is server or client side. Sorry! – Tomarto Jul 20 '12 at 14:30
  • 1
    @RuteshMakhijani I have a similar requirement with high number of records, please explain the reason behind using server side pagination for high number of records – Dinesh P.R. Nov 27 '12 at 16:26

6 Answers6

217

If you have not too much data, you can definitely do pagination by just storing all the data in the browser and filtering what's visible at a certain time.

Here's a simple pagination example from the list of fiddles on the angular.js Github wiki, which should be helpful:

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

function MyCtrl($scope) {
    $scope.currentPage = 0;
    $scope.pageSize = 10;
    $scope.data = [];
    $scope.numberOfPages=function(){
        return Math.ceil($scope.data.length/$scope.pageSize);                
    }
    for (var i=0; i<45; i++) {
        $scope.data.push("Item "+i);
    }
}

//We already have a limitTo filter built-in to angular,
//let's make a startFrom filter
app.filter('startFrom', function() {
    return function(input, start) {
        start = +start; //parse to int
        return input.slice(start);
    }
});
<div ng-controller="MyCtrl">
    <ul>
        <li ng-repeat="item in data | startFrom:currentPage*pageSize | limitTo:pageSize">
            {{item}}
        </li>
    </ul>
    <button ng-disabled="currentPage == 0" ng-click="currentPage=currentPage-1">
        Previous
    </button>
    {{currentPage+1}}/{{numberOfPages()}}
    <button ng-disabled="currentPage >= data.length/pageSize - 1" ng-click="currentPage=currentPage+1">
        Next
    </button>
</div>
miken32
  • 42,008
  • 16
  • 111
  • 154
Andrew Joslin
  • 43,033
  • 21
  • 100
  • 75
  • 1
    Thank you very much! It is exactly what I was looking for. I saw that example earlier but it didn't work. Now I noticed there is a little syntax error. A bracket is missing after the "for" sentence. – Tomarto Jul 20 '12 at 15:49
  • I added the brackets and updated it on the wiki. But it should've worked without them. – Andrew Joslin Jul 20 '12 at 18:00
  • Sorry, my mistake. I forgot to add a bracket for my controller. Thanks! – Tomarto Jul 20 '12 at 19:30
  • @AndyJoslin I got input is undefined on `return input.slice(start);` when I first load the script, it works fine, but ... emmmm .... – zx1986 Oct 17 '13 at 08:40
  • 1
    oh! never mind. I add a `if (input?)` condition before return `input.slice(start)`, thanks Andy! – zx1986 Oct 17 '13 at 08:51
  • With the more recent versions of Angular you can pass `limitTo` a negative number, so you wouldn't actually need a custom filter at all, you'd just run `limitTo` twice. [jsfiddle](http://jsfiddle.net/2UeML/) – paulcpederson Apr 04 '14 at 23:45
  • 1
    Like Bart, I needed to pass paging info into a calling function to get pagable data - it is similar but different and might help in some cases. http://plnkr.co/edit/RcSso3verGtXwToilJ5a – Steve Black May 23 '14 at 15:49
  • hi, I took the demo and listed pages, and view all.. (I did that to be used in one of my projects) http://jsfiddle.net/2ZzZB/1776/ – Juan Mendez Feb 17 '15 at 17:19
  • 3
    Hi, this demo came so handy for a project I got involved. I needed to add the option to view all, or toggle pagination as well as show each page. So I extended the demo. thanks a lot. http://jsfiddle.net/juanmendez/m4dn2xrv/ – Juan Mendez Mar 02 '15 at 06:43
  • Hey @AndrewJoslin: Thanks for a brilliant answer. When you say "If you have not too much data", how much is too much? A list with say, 200 items. Is that too much to be handled on the client side from a performance standpoint? – Thilak Rao Jul 15 '15 at 10:50
  • I was having problem with angular-ui `uib-pagination` thanks for this. – Davut Gürbüz Jun 30 '16 at 01:46
  • How can i add server side pagination to the existing method – Praveen Jan 27 '17 at 06:46
39

I copied the accepted answer but added some Bootstrap classes to the HTML:

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

function MyCtrl($scope) {
    $scope.currentPage = 0;
    $scope.pageSize = 10;
    $scope.data = [];
    $scope.numberOfPages=function(){
        return Math.ceil($scope.data.length/$scope.pageSize);                
    }
    for (var i=0; i<45; i++) {
        $scope.data.push("Item "+i);
    }
}

//We already have a limitTo filter built-in to angular,
//let's make a startFrom filter
app.filter('startFrom', function() {
    return function(input, start) {
        start = +start; //parse to int
        return input.slice(start);
    }
});
<html xmlns:ng="http://angularjs.org" ng-app lang="en">
    <head>
        <meta charset="utf-8">
        <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap.no-icons.min.css" rel="stylesheet">
        <link href="http://netdna.bootstrapcdn.com/font-awesome/2.0/css/font-awesome.css" rel="stylesheet">
        <script src="http://code.angularjs.org/1.1.0/angular.min.js"></script>
    </head>
    <body>
    <script type="text/javascript">
        var sortingOrder = 'name';
    </script>

        <div ng-controller="ctrlRead">
            <div class="input-append">
                <input type="text" ng-model="query" ng-change="search()" class="input-large search-query" placeholder="Search">
            <span class="add-on"><i class="icon-search"></i></span>
            </div>
            <table class="table table-striped table-condensed table-hover">
                <thead>
                    <tr>
                        <th class="id">Id&nbsp;<a ng-click="sort_by('id')"><i class="icon-sort"></i></a></th>
                        <th class="name">Name&nbsp;<a ng-click="sort_by('name')"><i class="icon-sort"></i></a></th>
                        <th class="description">Description&nbsp;<a ng-click="sort_by('description')"><i class="icon-sort"></i></a></th>
                        <th class="field3">Field 3&nbsp;<a ng-click="sort_by('field3')"><i class="icon-sort"></i></a></th>
                        <th class="field4">Field 4&nbsp;<a ng-click="sort_by('field4')"><i class="icon-sort"></i></a></th>
                        <th class="field5">Field 5&nbsp;<a ng-click="sort_by('field5')"><i class="icon-sort"></i></a></th>
                    </tr>
                </thead>
                <tfoot>
                    <td colspan="6">
                        <div class="pagination pull-right">
                            <ul>
                                <li ng-class="{disabled: currentPage == 0}">
                                    <a href ng-click="prevPage()">« Prev</a>
                                </li>
                                <li ng-repeat="n in range(pagedItems.length)"
                                    ng-class="{active: n == currentPage}"
                                ng-click="setPage()">
                                    <a href ng-bind="n + 1">1</a>
                                </li>
                                <li ng-class="{disabled: currentPage == pagedItems.length - 1}">
                                    <a href ng-click="nextPage()">Next »</a>
                                </li>
                            </ul>
                        </div>
                    </td>
                </tfoot>
                <tbody>
                    <tr ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse">
                        <td>{{item.id}}</td>
                        <td>{{item.name}}</td>
                        <td>{{item.description}}</td>
                        <td>{{item.field3}}</td>
                        <td>{{item.field4}}</td>
                        <td>{{item.field5}}</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

http://jsfiddle.net/SAWsA/11/

miken32
  • 42,008
  • 16
  • 111
  • 154
Spir
  • 1,709
  • 1
  • 16
  • 27
  • 2
    Awesome job. Be good to take in one step further and make the column headings dynamic i.e. get all unique key values from the items json array and bind that to an ng-repeat instead of hard coding the values. Something like what I did here: http://jsfiddle.net/gavinfoley/t39ZP/ – GFoley83 Mar 18 '13 at 10:24
  • having all that code in controller just makes it less reusable - http://www.telerik.com/help/silverlight/raddataservicedatasource-overview.html Seems angular is missing: QueryableDomainServiceCollectionView , VirtualQueryableCollectionView, HierarchicalDataCollectionView – Leblanc Meneses Apr 14 '13 at 00:33
  • 1
    This is a really great solution, easy to adapt. Used it on a complex scenario where the amount of two binded elements had reached the thousands, and it really was not an option to refactor the entire code. You should update the answer with some code explaining the basic principles. And perhaps upgrade AngularJS and Bootstrap versions in the fiddle :) – davidkonrad Nov 30 '17 at 03:19
15

I've built a module that makes in-memory pagination incredibly simple.

It allows you to paginate by simply replacing ng-repeat with dir-paginate, specifying the items per page as a piped filter, and then dropping the controls wherever you like in the form of a single directive, <dir-pagination-controls>

To take the original example asked by Tomarto, it would look like this:

<ul class='phones'>
    <li class='thumbnail' dir-paginate='phone in phones | filter:searchBar | orderBy:orderProp | limitTo:limit | itemsPerPage: limit'>
            <a href='#/phones/{{phone.id}}' class='thumb'><img ng-src='{{phone.imageUrl}}'></a>
            <a href='#/phones/{{phone.id}}'>{{phone.name}}</a>
            <p>{{phone.snippet}}</p>
    </li>
</ul>

<dir-pagination-controls></dir-pagination-controls>

There is no need for any special pagination code in your controller. It's all handled internally by the module.

Demo: http://plnkr.co/edit/Wtkv71LIqUR4OhzhgpqL?p=preview

Source: dirPagination of GitHub

Michael Bromley
  • 4,792
  • 4
  • 35
  • 57
  • JFI: Instead of separated dirPagination.tpl.html, we can also include the pagination code inside
      ...
    – npcoder Jan 18 '18 at 20:23
5

I know this thread is old now but I am answering it to keep things a bit updated.

With Angular 1.4 and above you can directly use limitTo filter which apart from accepting the limit parameter also accepts a begin parameter.

Usage: {{ limitTo_expression | limitTo : limit : begin}}

So now you may not need to use any third party library to achieve something like pagination. I have created a fiddle to illustrate the same.

Bharat Gupta
  • 2,628
  • 1
  • 19
  • 27
3

Check out this directive: https://github.com/samu/angular-table

It automates sorting and pagination a lot and gives you enough freedom to customize your table/list however you want.

Samuel Müller
  • 1,167
  • 1
  • 10
  • 12
  • At first glance this looked like exactly what I needed, but I can't seem to get it to work w/ $resource. It looks like it always thinks my list is empty here: https://github.com/ssmm/angular-table/blob/master/coffee/atPagination.coffee#L32 ... haven't been able to figure out why yet. :/ – Mike Desjardins Jul 29 '13 at 18:24
  • how do I show Sno in the first column since I can't use `$index` and my array does not contain incremental values – Dwigh Sep 27 '18 at 10:40
  • i did this: `{{sortedAndPaginatedList.indexOf(item) + 1}}` but I don't know if it is the right way – Dwigh Sep 27 '18 at 10:43
2

Here is a demo code where there is pagination + Filtering with AngularJS :

https://codepen.io/lamjaguar/pen/yOrVym

JS :

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

// alternate - https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination
// alternate - http://fdietz.github.io/recipes-with-angular-js/common-user-interface-patterns/paginating-through-client-side-data.html

app.controller('MyCtrl', ['$scope', '$filter', function ($scope, $filter) {
    $scope.currentPage = 0;
    $scope.pageSize = 10;
    $scope.data = [];
    $scope.q = '';

    $scope.getData = function () {
      // needed for the pagination calc
      // https://docs.angularjs.org/api/ng/filter/filter
      return $filter('filter')($scope.data, $scope.q)
     /* 
       // manual filter
       // if u used this, remove the filter from html, remove above line and replace data with getData()

        var arr = [];
        if($scope.q == '') {
            arr = $scope.data;
        } else {
            for(var ea in $scope.data) {
                if($scope.data[ea].indexOf($scope.q) > -1) {
                    arr.push( $scope.data[ea] );
                }
            }
        }
        return arr;
       */
    }

    $scope.numberOfPages=function(){
        return Math.ceil($scope.getData().length/$scope.pageSize);                
    }

    for (var i=0; i<65; i++) {
        $scope.data.push("Item "+i);
    }
  // A watch to bring us back to the 
  // first pagination after each 
  // filtering
$scope.$watch('q', function(newValue,oldValue){             if(oldValue!=newValue){
      $scope.currentPage = 0;
  }
},true);
}]);

//We already have a limitTo filter built-in to angular,
//let's make a startFrom filter
app.filter('startFrom', function() {
    return function(input, start) {
        start = +start; //parse to int
        return input.slice(start);
    }
});

HTML :

<div ng-app="myApp" ng-controller="MyCtrl">
  <input ng-model="q" id="search" class="form-control" placeholder="Filter text">
  <select ng-model="pageSize" id="pageSize" class="form-control">
        <option value="5">5</option>
        <option value="10">10</option>
        <option value="15">15</option>
        <option value="20">20</option>
     </select>
  <ul>
    <li ng-repeat="item in data | filter:q | startFrom:currentPage*pageSize | limitTo:pageSize">
      {{item}}
    </li>
  </ul>
  <button ng-disabled="currentPage == 0" ng-click="currentPage=currentPage-1">
        Previous
    </button> {{currentPage+1}}/{{numberOfPages()}}
  <button ng-disabled="currentPage >= getData().length/pageSize - 1" ng-click="currentPage=currentPage+1">
        Next
    </button>
</div>
Brahim LAMJAGUAR
  • 1,254
  • 5
  • 19
  • 28