Pagination on a list using ng-repeat
Asked Answered
D

6

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}
        });
    });
Digenesis answered 20/7, 2012 at 14:17 Comment(3)
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.Holarctic
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!Digenesis
@RuteshMakhijani I have a similar requirement with high number of records, please explain the reason behind using server side pagination for high number of recordsSowell
D
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>
Dworman answered 20/7, 2012 at 15:7 Comment(12)
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.Digenesis
I added the brackets and updated it on the wiki. But it should've worked without them.Dworman
Sorry, my mistake. I forgot to add a bracket for my controller. Thanks!Digenesis
@AndyJoslin I got input is undefined on return input.slice(start); when I first load the script, it works fine, but ... emmmm ....Guillory
oh! never mind. I add a if (input?) condition before return input.slice(start), thanks Andy!Guillory
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. jsfiddleWeidner
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. plnkr.co/edit/RcSso3verGtXwToilJ5aIsochronous
hi, I took the demo and listed pages, and view all.. (I did that to be used in one of my projects) jsfiddle.net/2ZzZB/1776Conflagration
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. jsfiddle.net/juanmendez/m4dn2xrvConflagration
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?Bowline
I was having problem with angular-ui uib-pagination thanks for this.Resa
How can i add server side pagination to the existing methodMutton
F
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/

Freida answered 1/10, 2012 at 9:7 Comment(3)
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: jsfiddle.net/gavinfoley/t39ZPPresentable
having all that code in controller just makes it less reusable - telerik.com/help/silverlight/… Seems angular is missing: QueryableDomainServiceCollectionView , VirtualQueryableCollectionView, HierarchicalDataCollectionViewBak
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 :)Scutcheon
A
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

Aerolite answered 10/7, 2014 at 15:30 Comment(1)
JFI: Instead of separated dirPagination.tpl.html, we can also include the pagination code inside <dir-pagination-controls> <ul class="pagination" ng-if="1 < pages.length || !autoHide"> ... </ul></dir-pagination-controls>Lacerta
A
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.

Atmosphere answered 2/2, 2017 at 15:9 Comment(1)
The fiddle you linked to doesn't actually use the begin parameter.Oodles
C
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.

Chomp answered 22/6, 2013 at 10:28 Comment(3)
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: github.com/ssmm/angular-table/blob/master/coffee/… ... haven't been able to figure out why yet. :/Garber
how do I show Sno in the first column since I can't use $index and my array does not contain incremental valuesYahwistic
i did this: <td at-sortable at-attribute="index">{{sortedAndPaginatedList.indexOf(item) + 1}}</td> but I don't know if it is the right wayYahwistic
S
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>
Shrewmouse answered 13/5, 2016 at 11:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.