259

I have a dataset of about 1000 items in-memory and am attempting to create a pager for this dataset, but I'm not sure on how to do this.

I'm using a custom filter function to filter the results, and that works fine, but somehow I need to get the number of pages out.

Any clues?

S.A.Norton Stanley
  • 1,833
  • 3
  • 23
  • 37
Micael
  • 6,950
  • 6
  • 25
  • 28

22 Answers22

290

Angular UI Bootstrap - Pagination Directive

Check out UI Bootstrap's pagination directive. I ended up using it rather than what is posted here as it has enough features for my current use and has a thorough test spec to accompany it.

View

<!-- table here -->

<pagination 
  ng-model="currentPage"
  total-items="todos.length"
  max-size="maxSize"  
  boundary-links="true">
</pagination>

<!-- items/page select here if you like -->

Controller

todos.controller("TodoController", function($scope) {
   $scope.filteredTodos = []
  ,$scope.currentPage = 1
  ,$scope.numPerPage = 10
  ,$scope.maxSize = 5;

  $scope.makeTodos = function() {
    $scope.todos = [];
    for (i=1;i<=1000;i++) {
      $scope.todos.push({ text:"todo "+i, done:false});
    }
  };
  $scope.makeTodos(); 

  $scope.$watch("currentPage + numPerPage", function() {
    var begin = (($scope.currentPage - 1) * $scope.numPerPage)
    , end = begin + $scope.numPerPage;

    $scope.filteredTodos = $scope.todos.slice(begin, end);
  });
});

I have made a working plunker for reference.


Legacy Version:

View

<!-- table here -->

<div data-pagination="" data-num-pages="numPages()" 
  data-current-page="currentPage" data-max-size="maxSize"  
  data-boundary-links="true"></div>

<!-- items/page select here if you like -->

Controller

todos.controller("TodoController", function($scope) {
   $scope.filteredTodos = []
  ,$scope.currentPage = 1
  ,$scope.numPerPage = 10
  ,$scope.maxSize = 5;

  $scope.makeTodos = function() {
    $scope.todos = [];
    for (i=1;i<=1000;i++) {
      $scope.todos.push({ text:"todo "+i, done:false});
    }
  };
  $scope.makeTodos(); 

  $scope.numPages = function () {
    return Math.ceil($scope.todos.length / $scope.numPerPage);
  };

  $scope.$watch("currentPage + numPerPage", function() {
    var begin = (($scope.currentPage - 1) * $scope.numPerPage)
    , end = begin + $scope.numPerPage;

    $scope.filteredTodos = $scope.todos.slice(begin, end);
  });
});

I have made a working plunker for reference.

Scotty.NET
  • 12,533
  • 4
  • 42
  • 51
88

I recently implemented paging for the Built with Angular site. You chan checkout the source: https://github.com/angular/builtwith.angularjs.org

I'd avoid using a filter to separate the pages. You should break up the items into pages within the controller.

btford
  • 5,631
  • 2
  • 29
  • 26
  • 61
    The solution is spread across multiple files. You need to look in at least the controller and the view. I don't see how that warrants a downvote: `Use your downvotes whenever you encounter an egregiously sloppy, no-effort-expended post, or an answer that is clearly and perhaps dangerously incorrect.` – btford Jun 26 '12 at 17:31
  • 2
    You can start looking at the – Jorge Nunez Newton Apr 17 '13 at 15:00
  • 6
    @btford Why would you avoid using a filter? – CWSpear Jul 27 '13 at 23:54
  • 4
    I up-voted to counteract the previous down vote because I felt that the poster provided a quality example that can be used to answer the original question. – RachelD Nov 20 '13 at 17:05
  • 1
    @btford Is it still a bad idea to paginate using a filter? Here's a plunkr paginating a list via a filter that seems performant (at least in this trivial example up to 10 000 000 rows): http://embed.plnkr.co/iWxWlCEvd6Uh8erUOyaF – Ryan Kimber Sep 29 '14 at 20:46
79

I've had to implement pagination quite a few times with Angular, and it was always a bit of a pain for something that I felt could be simplified. I've used some of the ideas presented here and elsewhere to make a pagination module that makes pagination as simple as:

<ul>
    <li dir-paginate="item in items | itemsPerPage: 10">{{ item }}</li>
</ul>

// then somewhere else on the page ....

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

That's it. It has the following features:

  • No custom code needed in your controller to tie the collection items to the pagination links.
  • You aren't bound to using a table or gridview - you can paginate anything you can ng-repeat!
  • Delegates to ng-repeat, so you can use any expression that could be validly used in an ng-repeat, including filtering, ordering etc.
  • Works across controllers - the pagination-controls directive does not need to know anything about the context in which the paginate directive is called.

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

For those who are looking for a "plug and play" solution, I think you'll find this useful.

Code

The code is available here on GitHub and includes a pretty good set of tests:

https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination

If you are interested I also wrote a short piece with a little more insight into the design of the module: http://www.michaelbromley.co.uk/blog/108/paginate-almost-anything-in-angularjs/

Community
  • 1
  • 1
Michael Bromley
  • 4,792
  • 4
  • 35
  • 57
  • Hi @michael bromley, I am trying with angularUtils. I have added dirPangination.js and dirPagination.tpl.html files to my project. But I started getting error like " [$compile:tpload] Failed to load template: directives/pagination/dirPagination.tpl.html". I had tried to put this html file in my project's directives folder. But I had no success. I have following doubts: 1. Where to put dirPagination.tpl.html in project( As I am using ruby on rails with Angularjs)? – Vieenay Siingh Oct 10 '14 at 08:40
  • Hi @michael bromley,is dir-paginate neccessary to include in order to use dir-pagination-controls? – Vieenay Siingh Oct 10 '14 at 08:55
  • @Vieenay Please open an [issue on the GitHub repo](https://github.com/michaelbromley/angularUtils/issues) - that's a better forum for this kind of question. – Michael Bromley Oct 10 '14 at 09:06
  • Hi @michael bromley, I think this is not bug. But I dont know where to put dirPagination.tpl.htm in project. I tried to put dirPagination.tpl.htm file in pagination folder with in directives folder. But It did not work for me. Could you please suggest me where to put html file. – Vieenay Siingh Oct 10 '14 at 09:29
  • Hi @michael, I have set template-url="directives/pagination/dirPagination.tpl.html" in html page and added dirPagination.tpl.html file in pagination folder with in directives folder of Angularjs. But still I am getting Error: [$compile:tpload] Failed to load template: directives/pagination/dirPagination.tpl.html. Am I missing something here? – Vieenay Siingh Oct 10 '14 at 10:06
  • @michael. Your directives work perfectly good. But I need to figure out how to save a state of the pagination. In my situation I have an array of links with details. When I go by a link and than return back, my previous pagination state is reset. Any idea how I could fix it? – burseaner Jan 26 '15 at 17:57
  • @burseaner Hi, I suggest you open an issue on the GitHub repo - it is a better forum for this kind of discussion https://github.com/michaelbromley/angularUtils/issues – Michael Bromley Jan 27 '15 at 09:11
  • 2
    Neat, you gained me at the moment I read that the pagination could be anywhere in the page :) Currently using it and working smoothly. – Diosney Feb 06 '15 at 22:49
  • 4
    This is the best paging directive out there for angular. It's probably the simplest solution for paging I've ever seen. I was up and paging multiple tables per view each with their own isolated paging control in 15 minutes. All with two lines of code per .jade file. All I can say is WOW. Awesome! – jrista Apr 22 '15 at 21:21
  • 6
    I can vouch for this directive's awesomeness, I had a complex ng-repeat and it handled it no problems. Super easy setup. – gkiely Jun 02 '15 at 07:47
  • 1
    Your "tracker()" method save my day. I was having an awful and rare behavior without it. – Leopoldo Sanczyk Aug 16 '16 at 17:47
  • @DouglasGaskell MIT – Michael Bromley Jun 27 '17 at 10:53
  • updated the pagination to bootstrap 4 pagination classes. otherwise very useful library for legacy code –  Dec 10 '18 at 14:28
  • Super easy setup, great directive, was easy to style it the way I wanted and came working 'out-of-the-box'. – joaolell Aug 25 '22 at 19:43
64

I just made a JSFiddle that show pagination + search + order by on each column using btford code: http://jsfiddle.net/SAWsA/11/

Spir
  • 1,709
  • 1
  • 16
  • 27
  • The ordering of the numbers is a little off, but overall, looks very solid. – Narretz Oct 04 '12 at 07:30
  • Indeed the `$filter('orderBy')` take the number as string. Don't know how to fix that – Spir Oct 05 '12 at 07:44
  • I think it's because they are strings in the object array. ;) – Narretz Oct 05 '12 at 08:16
  • does this require all the data to be fetched from the beginning? – Aladdin Mhemed Nov 11 '12 at 15:29
  • Nope you can add item to `$scope.items` collection whenever you want. It's AngularJS binding :) – Spir Nov 14 '12 at 12:36
  • @Narretz Looks like it users OrderBy = 'name' to start out with (which is a string) so it's....if you change it to orderBy = 'id' and change the id's to not be strings, the order works fine for the numbers – Logan W Jan 04 '13 at 20:26
  • 4
    Thanks for the fiddle. Its very useful. Question though: how would you implement the sort over the entire result set instead of what's on the current page? – super9 May 07 '13 at 11:19
  • What if you wanted to paginate a set of filtered results? e.g. `ng-repeat = "thing in things | filter: searchTerm | limitTo: 50"` Is it still worth it to pre-paginate within the controller? – Will Koper May 07 '13 at 16:15
  • 5
    Note that the sorting only works on the current page... It doesn't sort the whole array. The pagination has to be redone everytime you change the sort order – dgn May 22 '13 at 11:02
  • Nope @scenario. Try searching for an item on the last page for example and it will show up when searching on the first page. It actually search in the whole collection not the paged[indice] items. But I know my example is lame since string are almost the same. I should write a better example sometimes. But you get the idea. – Spir May 23 '13 at 14:14
  • 3
    @Spir: Yes, searching works, but not sorting. If you reverse the sorting on page 1, only this page is reordered, instead of displaying the items 9, 20 and co – dgn May 27 '13 at 12:51
  • @scenario ah yes, like I said I should update the example. I'm using a modified version of it in production – Spir May 29 '13 at 09:10
  • Thanks. I had to add `$scope.search();` to the end of `$scope.sort_by = function(newSortingOrder) {` body to sort all the results/filtered results instead of just the results on the page of the pagination. – Aleck Landgraf Sep 19 '13 at 01:05
  • 1
    @AleckLandgraf i tried adding $scope.search but ii is still not showing correct sorted list . please let me know what else u tried or added – anam Apr 16 '14 at 05:29
  • 1
    @simmisimmi @Spir @scenario there's a bug at the bottom of the javascript: `new_sorting_order` should be `newSortingOrder`. Fix that, add the `@scope.search();`, and you'll see things sort as expected, and the sort icons update too. (Run the fiddle with your browser's debugging console open (in chrome, F12, console tab) and it's obvious). – Dax Fohl Jun 17 '14 at 16:03
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Jonathan Nixon Aug 28 '14 at 20:55
  • @Guthwulf, I'm aware of that. I should have added some part of the code in the response. Also jsfiddle.net is not known to delete page. Also this solution is 2 year old I think there are better way now to handle such feature. – Spir Aug 29 '14 at 07:24
15

I updated Scotty.NET's plunkr http://plnkr.co/edit/FUeWwDu0XzO51lyLAEIA?p=preview so that it uses newer versions of angular, angular-ui, and bootstrap.

Controller

var todos = angular.module('todos', ['ui.bootstrap']);

todos.controller('TodoController', function($scope) {
  $scope.filteredTodos = [];
  $scope.itemsPerPage = 30;
  $scope.currentPage = 4;

  $scope.makeTodos = function() {
    $scope.todos = [];
    for (i=1;i<=1000;i++) {
      $scope.todos.push({ text:'todo '+i, done:false});
    }
  };

  $scope.figureOutTodosToDisplay = function() {
    var begin = (($scope.currentPage - 1) * $scope.itemsPerPage);
    var end = begin + $scope.itemsPerPage;
    $scope.filteredTodos = $scope.todos.slice(begin, end);
  };

  $scope.makeTodos(); 
  $scope.figureOutTodosToDisplay();

  $scope.pageChanged = function() {
    $scope.figureOutTodosToDisplay();
  };

});

Bootstrap UI component

 <pagination boundary-links="true" 
    max-size="3" 
    items-per-page="itemsPerPage"
    total-items="todos.length" 
    ng-model="currentPage" 
    ng-change="pageChanged()"></pagination>
Community
  • 1
  • 1
user2176745
  • 491
  • 4
  • 11
10

This is a pure javascript solution that I've wrapped as an Angular service to implement pagination logic like in google search results.

Working demo on CodePen at http://codepen.io/cornflourblue/pen/KVeaQL/

Details and explanation at this blog post

function PagerService() {
    // service definition
    var service = {};

    service.GetPager = GetPager;

    return service;

    // service implementation
    function GetPager(totalItems, currentPage, pageSize) {
        // default to first page
        currentPage = currentPage || 1;

        // default page size is 10
        pageSize = pageSize || 10;

        // calculate total pages
        var totalPages = Math.ceil(totalItems / pageSize);

        var startPage, endPage;
        if (totalPages <= 10) {
            // less than 10 total pages so show all
            startPage = 1;
            endPage = totalPages;
        } else {
            // more than 10 total pages so calculate start and end pages
            if (currentPage <= 6) {
                startPage = 1;
                endPage = 10;
            } else if (currentPage + 4 >= totalPages) {
                startPage = totalPages - 9;
                endPage = totalPages;
            } else {
                startPage = currentPage - 5;
                endPage = currentPage + 4;
            }
        }

        // calculate start and end item indexes
        var startIndex = (currentPage - 1) * pageSize;
        var endIndex = startIndex + pageSize;

        // create an array of pages to ng-repeat in the pager control
        var pages = _.range(startPage, endPage + 1);

        // return object with all pager properties required by the view
        return {
            totalItems: totalItems,
            currentPage: currentPage,
            pageSize: pageSize,
            totalPages: totalPages,
            startPage: startPage,
            endPage: endPage,
            startIndex: startIndex,
            endIndex: endIndex,
            pages: pages
        };
    }
}
Jason Watmore
  • 4,521
  • 2
  • 32
  • 36
  • I used your approach but problem is if I want to use index-es for ordering on page, it's always shown as 0-9... – vaske Nov 09 '16 at 23:48
5

Below solution quite simple.

<pagination  
        total-items="totalItems" 
        items-per-page= "itemsPerPage"
        ng-model="currentPage" 
        class="pagination-sm">
</pagination>

<tr ng-repeat="country in countries.slice((currentPage -1) * itemsPerPage, currentPage * itemsPerPage) "> 

Here is sample jsfiddle

Sh4m
  • 1,424
  • 12
  • 30
4

I've extracted the relevant bits here. This is a 'no frills' tabular pager, so sorting or filtering is not included. Feel free to change/add as needed:

     //your data source may be different. the following line is 
     //just for demonstration purposes only
    var modelData = [{
      text: 'Test1'
    }, {
      text: 'Test2'
    }, {
      text: 'Test3'
    }];

    (function(util) {

      util.PAGE_SIZE = 10;

      util.range = function(start, end) {
        var rng = [];

        if (!end) {
          end = start;
          start = 0;
        }

        for (var i = start; i < end; i++)
          rng.push(i);

        return rng;
      };

      util.Pager = function(data) {
        var self = this,
          _size = util.PAGE_SIZE;;

        self.current = 0;

        self.content = function(index) {
          var start = index * self.size,
            end = (index * self.size + self.size) > data.length ? data.length : (index * self.size + self.size);

          return data.slice(start, end);
        };

        self.next = function() {
          if (!self.canPage('Next')) return;
          self.current++;
        };

        self.prev = function() {
          if (!self.canPage('Prev')) return;
          self.current--;
        };

        self.canPage = function(dir) {
          if (dir === 'Next') return self.current < self.count - 1;
          if (dir === 'Prev') return self.current > 0;
          return false;
        };

        self.list = function() {
          var start, end;
          start = self.current < 5 ? 0 : self.current - 5;
          end = self.count - self.current < 5 ? self.count : self.current + 5;
          return Util.range(start, end);
        };

        Object.defineProperty(self, 'size', {
          configurable: false,
          enumerable: false,
          get: function() {
            return _size;
          },
          set: function(val) {
            _size = val || _size;
          }
        });

        Object.defineProperty(self, 'count', {
          configurable: false,
          enumerable: false,
          get: function() {
            return Math.ceil(data.length / self.size);
          }
        });
      };

    })(window.Util = window.Util || {});

    (function(ns) {
      ns.SampleController = function($scope, $window) {
        $scope.ModelData = modelData;
        //instantiate pager with array (i.e. our model)
        $scope.pages = new $window.Util.Pager($scope.ModelData);
      };
    })(window.Controllers = window.Controllers || {});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<table ng-controller="Controllers.SampleController">
  <thead>
    <tr>
      <th>
        Col1
      </th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="item in pages.content(pages.current)" title="{{item.text}}">
      <td ng-bind-template="{{item.text}}"></td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td colspan="4">
        <a href="#" ng-click="pages.prev()">&laquo;</a>
        <a href="#" ng-repeat="n in pages.list()" ng-click="pages.current = n" style="margin: 0 2px;">{{n + 1}}</a>
        <a href="#" ng-click="pages.next()">&raquo;</a>
      </td>
    </tr>
  </tfoot>
</table>
Mo.
  • 26,306
  • 36
  • 159
  • 225
msyed
  • 111
  • 1
  • 3
3

The jQuery Mobile angular adapter has a paging filter you could base off of.

Here's a demo fiddle that uses it (add more than 5 items and it becomes paged): http://jsfiddle.net/tigbro/Du2DY/

Here's the source: https://github.com/tigbro/jquery-mobile-angular-adapter/blob/master/src/main/webapp/utils/paging.js

Andrew Joslin
  • 43,033
  • 21
  • 100
  • 75
3

For anyone who find it difficult like me to create a paginator for a table I post this. So, in your view :

          <pagination total-items="total" items-per-page="itemPerPage"    ng-model="currentPage" ng-change="pageChanged()"></pagination>    
        <!-- To specify your choice of items Per Pages-->
     <div class="btn-group">
                <label class="btn btn-primary" ng-model="radioModel"  btn-radio="'Left'" data-ng-click="setItems(5)">5</label>
                <label class="btn btn-primary" ng-model="radioModel" btn-radio="'Middle'" data-ng-click="setItems(10)">10</label>
                <label class="btn btn-primary" ng-model="radioModel" btn-radio="'Right'" data-ng-click="setItems(15)">15</label>
            </div>
     //And don't forget in your table:
      <tr data-ng-repeat="p in profiles | offset: (currentPage-1)*itemPerPage | limitTo: itemPerPage" >

In your angularJs:

  var module = angular.module('myapp',['ui.bootstrap','dialogs']);
  module.controller('myController',function($scope,$http){
   $scope.total = $scope.mylist.length;     
   $scope.currentPage = 1;
   $scope.itemPerPage = 2;
   $scope.start = 0;

   $scope.setItems = function(n){
         $scope.itemPerPage = n;
   };
   // In case you can replace ($scope.currentPage - 1) * $scope.itemPerPage in <tr> by "start"
   $scope.pageChanged = function() {
        $scope.start = ($scope.currentPage - 1) * $scope.itemPerPage;
            };  
});
   //and our filter
     module.filter('offset', function() {
              return function(input, start) {
                start = parseInt(start, 10);
                return input.slice(start);
              };
            });     
K.Mouna
  • 83
  • 1
  • 7
  • There were answers with so many upvotes and positives.. but none worked for me.. but this one combined with @svarog 's answer worked like a charm for me. – Harshith Rai Dec 11 '18 at 04:53
3

I use this 3rd party pagination library and it works well. It can do local/remote datasources and it's very configurable.

https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination

<dir-pagination-controls
    [max-size=""]
    [direction-links=""]
    [boundary-links=""]
    [on-page-change=""]
    [pagination-id=""]
    [template-url=""]
    [auto-hide=""]>
    </dir-pagination-controls>
Henry Zou
  • 1,809
  • 1
  • 14
  • 19
3

You can easily do this using Bootstrap UI directive.

This answer is a modification of the answer given by @Scotty.NET, I have changed the code because <pagination> directive is deprecated now.

Following code generates the pagination:

<ul uib-pagination 
    boundary-links="true"  
    total-items="totalItems"  
    items-per-page="itemsPerPage"  
    ng-model="currentPage"  
    ng-change="pageChanged()"  
    class="pagination"  
    previous-text="&lsaquo;"  
    next-text="&rsaquo;"  
    first-text="&laquo;"  
    last-text="&raquo;">
</ul>

To make it functional, use this in your controller:

$scope.filteredData = []
$scope.totalItems = $scope.data.length;
$scope.currentPage = 1;
$scope.itemsPerPage = 5;

$scope.setPage = function (pageNo) {
    $scope.currentPage = pageNo;
};

$scope.pageChanged = function() {
    var begin = (($scope.currentPage - 1) * $scope.itemsPerPage)
    , end = begin + $scope.itemsPerPage;

    $scope.filteredData = $scope.data.slice(begin, end);
};

$scope.pageChanged();

Refer to this for more options of pagination: Bootstrap UI Pagination Directive

3

Since Angular 1.4, the limitTo filter also accepts a second optional argument begin

From the docs:

{{ limitTo_expression | limitTo : limit : begin}}

begin (optional) string|number
Index at which to begin limitation. As a negative index, begin indicates an offset from the end of input. Defaults to 0.

So you don't need to create a new directive, This argument can be used to set the offset of the pagination

ng-repeat="item in vm.items| limitTo: vm.itemsPerPage: (vm.currentPage-1)*vm.itemsPerPage" 
svarog
  • 9,477
  • 4
  • 61
  • 77
2

ng-repeat pagination

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

<script>

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

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

 $scope.getData = function () {

  return $filter('filter')($scope.data, $scope.q)

   }

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

   for (var i=0; i<65; i++) {
    $scope.data.push("Item "+i);
   }
  }]);

        app.filter('startFrom', function() {
    return function(input, start) {
    start = +start; //parse to int
    return input.slice(start);
   }
  });
  </script>
Asad
  • 21
  • 1
1

Previous messages recommended basically how to build a paging by yourself. If you are like me, and prefer a finished directive, I just found a great one called ngTable. It supports sorting, filtering and pagination.

It is a very clean solution, all you need in your view:

   <table ng-table="tableParams" class="table">
        <tr ng-repeat="user in $data">
            <td data-title="'Name'" sortable="'name'">
                {{user.name}}
            </td>
            <td data-title="'Age'" sortable="'age'">
                {{user.age}}
            </td>
        </tr>
    </table>

And in controller:

$scope.tableParams = new ngTableParams({
    page: 1,            // show first page
    count: 10,          // count per page
    sorting: {
        name: 'asc'     // initial sorting
    }
}, {
    total: data.length, // length of data
    getData: function($defer, params) {
        // use build-in angular filter
        var orderedData = params.sorting() ?
                            $filter('orderBy')(data, params.orderBy()) :
                            data;

        var start = (params.page() - 1) * params.count();
        var end = params.page() * params.count();

        $defer.resolve(orderedData.slice( start, end));
    }
});

Link to GitHub: https://github.com/esvit/ng-table/

Carlos Morales
  • 5,676
  • 4
  • 34
  • 42
1

Angular-Paging

is a wonderful choice

A directive to aid in paging large datasets while requiring the bare minimum of actual paging information. We are very dependant on the server for "filtering" results in this paging scheme. The central idea being we only want to hold the active "page" of items - rather than holding the entire list of items in memory and paging on the client-side.

sudo bangbang
  • 27,127
  • 11
  • 75
  • 77
sendreams
  • 389
  • 2
  • 13
1

Old question but since I think my approach is a bit different and less complex I will share this and hope that someone besides me find it useful.

What I found to be an easy and small solution to pagination is to combine a directive with a filter which uses the same scope variables.

To implement this you add the filter on the array and add the directiv like this

<div class="row">
    <table class="table table-hover">
        <thead>
            <tr>
                <th>Name</th>
                <th>Price</th>
                <th>Quantity</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="item in items | cust_pagination:p_Size:p_Step">
                <td>{{item.Name}}</td>
                <td>{{item.Price}}</td>
                <td>{{item.Quantity}}</td>
            </tr>
        </tbody>
    </table>
    <div cust-pagination p-items="items" p-boundarylinks="true" p-size="p_Size" p-step="p_Step"></div>
</div>

p_Size and p_Step are scope variables which can be customized in the scope else the default value of the p_Size is 5 and p_Step is 1.

When a step is change in the pagination the p_Step is updated and will trigger a new filtering by cust_pagination filter. The cust_pagination filter then slices the array depending on the p_Step value like below and only return the active records selected in the pagination section

var startIndex = nStep * nPageSize;
var endIndex = startIndex + nPageSize;
var arr = items.slice(startIndex, endIndex);
return arr;

DEMO View the complete solution in this plunker

Marcus Höglund
  • 16,172
  • 11
  • 47
  • 69
0

There is my example. Selected button in the middle on the list Controller. config >>>

 $scope.pagination = {total: null, pages: [], config: {count: 10, page: 1, size: 7}};

Logic for pagination:

/*
     Pagination
     */
    $scope.$watch('pagination.total', function (total) {
        if(!total || total <= $scope.pagination.config.count) return;
        _setPaginationPages(total);
    });

    function _setPaginationPages(total) {
        var totalPages = Math.ceil(total / $scope.pagination.config.count);
        var pages = [];
        var start = $scope.pagination.config.page - Math.floor($scope.pagination.config.size/2);
        var finish = null;

        if((start + $scope.pagination.config.size - 1) > totalPages){
            start = totalPages - $scope.pagination.config.size;
        }
        if(start <= 0) {
            start = 1;
        }

       finish = start +  $scope.pagination.config.size - 1;
       if(finish > totalPages){
           finish = totalPages;
       }


        for (var i = start; i <= finish; i++) {
            pages.push(i);
        }

        $scope.pagination.pages = pages;
    }

    $scope.$watch("pagination.config.page", function(page){
        _setPaginationPages($scope.pagination.total);
        _getRespondents($scope.pagination.config);
    });

and my view on bootstap

<ul ng-class="{hidden: pagination.total == 0}" class="pagination">
        <li ng-click="pagination.config.page = pagination.config.page - 1"
            ng-class="{disabled: pagination.config.page == 1}" ><a href="#">&laquo;</a></li>
        <li ng-repeat="p in pagination.pages"
            ng-click="pagination.config.page = p"
            ng-class="{active: p == pagination.config.page}"><a href="#">{{p}}</a></li>
        <li ng-click="pagination.config.page = pagination.config.page + 1"
            ng-class="{disabled: pagination.config.page == pagination.pages.length}"><a href="#">&raquo;</a></li>
    </ul >

It is useful

Alexey
  • 11
  • 2
0

I wish I could comment, but I'll just have to leave this here:

Scotty.NET's answer and user2176745's redo for later versions are both great, but they both miss something that my version of AngularJS (v1.3.15) breaks on:

i is not defined in $scope.makeTodos.

As such, replacing with this function fixes it for more recent angular versions.

$scope.makeTodos = function() {
    var i;
    $scope.todos = [];
    for (i=1;i<=1000;i++) {
        $scope.todos.push({ text:'todo '+i, done:false});
    }
};
Lewis
  • 624
  • 9
  • 16
0

Overview : Pagination using

 - ng-repeat
 - uib-pagination

View :

<div class="row">
    <div class="col-lg-12">
        <table class="table">
            <thead style="background-color: #eee">
                <tr>
                    <td>Dispature</td>
                    <td>Service</td>
                    <td>Host</td>
                    <td>Value</td>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="x in app.metricsList">
                    <td>{{x.dispature}}</td>
                    <td>{{x.service}}</td>
                    <td>{{x.host}}</td>
                    <td>{{x.value}}</td>
                </tr>
            </tbody>
        </table>

        <div align="center">
            <uib-pagination items-per-page="app.itemPerPage" num-pages="numPages"
                total-items="app.totalItems" boundary-link-numbers="true"
                ng-model="app.currentPage" rotate="false" max-size="app.maxSize"
                class="pagination-sm" boundary-links="true"
                ng-click="app.getPagableRecords()"></uib-pagination>        

            <div style="float: right; margin: 15px">
                <pre>Page: {{app.currentPage}} / {{numPages}}</pre>
            </div>          
        </div>
    </div>
</div>

JS Controller :

app.controller('AllEntryCtrl',['$scope','$http','$timeout','$rootScope', function($scope,$http,$timeout,$rootScope){

    var app = this;
    app.currentPage = 1;
    app.maxSize = 5;
    app.itemPerPage = 5;
    app.totalItems = 0;

    app.countRecords = function() {
        $http.get("countRecord")
        .success(function(data,status,headers,config){
            app.totalItems = data;
        })
        .error(function(data,status,header,config){
            console.log(data);
        });
    };

    app.getPagableRecords = function() {
        var param = {
                page : app.currentPage,
                size : app.itemPerPage  
        };
        $http.get("allRecordPagination",{params : param})
        .success(function(data,status,headers,config){
            app.metricsList = data.content;
        })
        .error(function(data,status,header,config){
            console.log(data);
        });
    };

    app.countRecords();
    app.getPagableRecords();

}]);
Riddhi Gohil
  • 1,758
  • 17
  • 17
0

I would like to add my solution that works with ngRepeat and filters that you use with it without using a $watch or a sliced array.

Your filter results will be paginated!

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

app.controller('myController', ['$scope', function($scope){
    $scope.list= ['a', 'b', 'c', 'd', 'e'];

    $scope.pagination = {
        currentPage: 1,
        numPerPage: 5,
        totalItems: 0
    };

    $scope.searchFilter = function(item) {
        //Your filter results will be paginated!
        //The pagination will work even with other filters involved
        //The total number of items in the result of your filter is accounted for
    };

    $scope.paginationFilter = function(item, index) {
        //Every time the filter is used it restarts the totalItems
        if(index === 0) 
            $scope.pagination.totalItems = 0;

        //This holds the totalItems after the filters are applied
        $scope.pagination.totalItems++;

        if(
            index >= (($scope.pagination.currentPage - 1) * $scope.pagination.numPerPage)
            && index < ((($scope.pagination.currentPage - 1) * $scope.pagination.numPerPage) + $scope.pagination.numPerPage)
        )
            return true; //return true if item index is on the currentPage

        return false;
    };
}]);

In the HTML make sure that you apply your filters to the ngRepeat before the pagination filter.

<table data-ng-controller="myController">
    <tr data-ng-repeat="item in list | filter: searchFilter | filter: paginationFilter track by $index">
        <td>
            {{item}}
        </td>
    <tr>
</table>
<ul class="pagination-sm"
    uib-pagination
    data-boundary-links="true"
    data-total-items="pagination.totalItems"
    data-items-per-page="pagination.numPerPage"
    data-ng-model="pagination.currentPage"
    data-previous-text="&lsaquo;"
    data-next-text="&rsaquo;"
    data-first-text="&laquo;"
    data-last-text="&raquo;">
 </ul>
Jonathan Czitkovics
  • 1,642
  • 11
  • 11
0

This below code will help for providing custom paging in backend with angular repeat.

Your data will be in

$scope.myticketIssuesData = [];
$scope.allticketIssuesData = [];

var jiraapp = angular.module('jiraapp',  ['ui.bootstrap']);

jiraapp.controller('JiraController', ['$scope', '$http', '$window','$location', function JiraController($scope, $http, $window,$location) {
    
    $scope.myticketIssuesData = [];
    $scope.allticketIssuesData = [];
    $scope.jiraIssue = {};
    $scope.RequesterType = [];
    $scope.loading = false;
    $scope.showerror = false;
    $scope.alert = {};

    $scope.maxSize = 10;    
    $scope.totalCount = 0;  
    $scope.pageIndex = 0;  
    $scope.startIndex = 0;
    $scope.pageSizeSelected = 10;

    $scope.maxallSize = 10;
    $scope.totalallCount = 0;
    $scope.pageallIndex = 0; 
    $scope.startallIndex = 0;
    $scope.pageallSizeSelected = 10;

    $scope.getUserTickets = function()  {
        $scope.loading = true;
        $http({
            method: 'GET',
            url: 'http://localhost:53583/api/Jira/getUserTickets?assignee='+$scope.loc+'&startAt='+ $scope.startIndex +'&maxResults='+$scope.pageSizeSelected,
            headers: {
                "Accept": "application/json",
                "Access-Control-Allow-Origin": "http://localhost:8080",
                "crossDomain": "true",
            }
        }).then(function successCallback(response) {
            
            $scope.myticketIssuesData = response.data.issues;
            $scope.totalCount = response.data.total;
            
            $scope.loading = false;
           
        }, function errorCallback(response) {
            $scope.loading = false;
           
        });
    }
    
    $scope.getrequestType = function(){
        $http({
            method: 'GET',
            url: 'http://localhost:53583/api/Jira/getrequestType',
            headers: {
                "Accept": "application/json",
                "Access-Control-Allow-Origin": "http://localhost:8080",
                "crossDomain": "true",
            }
        }).then(function successCallback(response) {
            $scope.RequesterType = response.data.values;
        }, function errorCallback(response) {
        });
    }

    $scope.getDropDown = function(){
        $scope.getrequestType();
    }

    $scope.initialize = function (item) {
        $scope.getUserTickets();
        $scope.getDropDown();
    }

    $scope.initialize();

    $scope.pageChanged = function () {  

        if($scope.pageIndex == 0)
            $scope.startIndex = 0;
        else if($scope.pageIndex == 1)
            $scope.startIndex = 0;
        else
            $scope.startIndex =  (($scope.pageIndex-1) * $scope.pageSizeSelected);

        $scope.getUserTickets();
    };  

    $scope.pageallChanged = function () {  

        if($scope.pageallIndex == 0)
            $scope.startallIndex = 0;
        else if($scope.pageallIndex == 1)
            $scope.startallIndex = 0;
        else
            $scope.startallIndex =  (($scope.pageallIndex-1) * $scope.pageallSizeSelected);
        $scope.getAllTickets();
    };  

    $scope.changeallPageSize = function () {  
        $scope.pageallIndex = 0;  
        $scope.getAllTickets();
    };  

    $scope.getAllTickets = function()  {
        $scope.loading = true;
        $http({
            method: 'GET',
            url: 'http://localhost:53583/api/Jira/getAllTickets?startAt='+ $scope.startallIndex +'&maxResults='+$scope.pageallSizeSelected,
            headers: {
                "Accept": "application/json",
                "Access-Control-Allow-Origin": "http://localhost:8080",
                "crossDomain": "true",
            }
        }).then(function successCallback(response) {
            
            $scope.allticketIssuesData = response.data.issues;
            $scope.totalallCount = response.data.total;
            
            $scope.loading = false;
           
        }, function errorCallback(response) {
            
            $scope.loading = false;
            

        });
    }


}]);
<html ng-app="jiraapp">

<head>
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
    crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
    <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro' rel='stylesheet' type='text/css'>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
        crossorigin="anonymous"></script>

    <script src="/angular.min.js"></script>
    <script src="/jira.js"></script>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.6/angular-route.min.js"></script>
    
    <script src="/ui-bootstrap-tpls-0.13.4.min.js"></script>

    <!-- this is important -->
    <style type="text/css">
        #loading {
            position: fixed;
            top: 50%;
            left: 50%;
            margin-top: -5em;
            margin-left: -10em;
        }

        .pagination {
            display: inline-block;
            padding-left: 0;
            margin: 20px 0;
            border-radius: 4px
        }

        .pagination>li {
            display: inline
        }

        .pagination>li>a,
        .pagination>li>span {
            position: relative;
            float: left;
            padding: 6px 12px;
            margin-left: -1px;
            line-height: 1.42857143;
            color: #337ab7;
            text-decoration: none;
            background-color: #fff;
            border: 1px solid #ddd
        }

        .pagination>li:first-child>a,
        .pagination>li:first-child>span {
            margin-left: 0;
            border-top-left-radius: 4px;
            border-bottom-left-radius: 4px
        }

        .pagination>li:last-child>a,
        .pagination>li:last-child>span {
            border-top-right-radius: 4px;
            border-bottom-right-radius: 4px
        }

        .pagination>li>a:focus,
        .pagination>li>a:hover,
        .pagination>li>span:focus,
        .pagination>li>span:hover {
            z-index: 3;
            color: #23527c;
            background-color: #eee;
            border-color: #ddd
        }

        .pagination>.active>a,
        .pagination>.active>a:focus,
        .pagination>.active>a:hover,
        .pagination>.active>span,
        .pagination>.active>span:focus,
        .pagination>.active>span:hover {
            z-index: 2;
            color: #fff;
            cursor: default;
            background-color: #337ab7;
            border-color: #337ab7
        }

        .pagination>.disabled>a,
        .pagination>.disabled>a:focus,
        .pagination>.disabled>a:hover,
        .pagination>.disabled>span,
        .pagination>.disabled>span:focus,
        .pagination>.disabled>span:hover {
            color: #777;
            cursor: not-allowed;
            background-color: #fff;
            border-color: #ddd
        }

        .pagination-lg>li>a,
        .pagination-lg>li>span {
            padding: 10px 16px;
            font-size: 18px;
            line-height: 1.3333333
        }

        .pagination-lg>li:first-child>a,
        .pagination-lg>li:first-child>span {
            border-top-left-radius: 6px;
            border-bottom-left-radius: 6px
        }

        .pagination-lg>li:last-child>a,
        .pagination-lg>li:last-child>span {
            border-top-right-radius: 6px;
            border-bottom-right-radius: 6px
        }

        .pagination-sm>li>a,
        .pagination-sm>li>span {
            padding: 5px 10px;
            font-size: 12px;
            line-height: 1.5
        }

        .pagination-sm>li:first-child>a,
        .pagination-sm>li:first-child>span {
            border-top-left-radius: 3px;
            border-bottom-left-radius: 3px
        }

        .pagination-sm>li:last-child>a,
        .pagination-sm>li:last-child>span {
            border-top-right-radius: 3px;
            border-bottom-right-radius: 3px
        }

        .pager {
            padding-left: 0;
            margin: 20px 0;
            text-align: center;
            list-style: none
        }

        .pager li {
            display: inline
        }

        .pager li>a,
        .pager li>span {
            display: inline-block;
            padding: 5px 14px;
            background-color: #fff;
            border: 1px solid #ddd;
            border-radius: 15px
        }

        .pager li>a:focus,
        .pager li>a:hover {
            text-decoration: none;
            background-color: #eee
        }

        .pager .next>a,
        .pager .next>span {
            float: right
        }

        .pager .previous>a,
        .pager .previous>span {
            float: left
        }

        .pager .disabled>a,
        .pager .disabled>a:focus,
        .pager .disabled>a:hover,
        .pager .disabled>span {
            color: #777;
            cursor: not-allowed;
            background-color: #fff
        }
    </style>
</head>

<body ng-controller="JiraController">
    <div class="col-sm-12">
        <div class="row" style="background: #09c;">
            <div style="margin-left: auto; margin-right: auto;">
                <img src="/logo.png" height="80">
                <span class="d-none d-sm-inline"
                    style="color: white; font-size: 4rem; vertical-align: middle; font-family:'Source Code Pro'">Jira</span>
            </div>
        </div>
        <div class="row">
            <div class="col-sm-12">
                <nav>
                    <div class="nav nav-tabs" id="nav-tab" role="tablist">
                        <a class="nav-item nav-link active" id="nav-myticket-tab" data-toggle="tab" href="#nav-myticket"
                            role="tab" aria-controls="nav-myticket" aria-selected="true" ng-click="getUserTickets()">My
                            Ticket</a>
                    </div>
                </nav>
                <div class="tab-content" id="nav-tabContent">
                    <div class="tab-pane fade show active" id="nav-myticket" role="tabpanel"
                        aria-labelledby="nav-myticket-tab">
                        <div class="col-sm-12" style="margin:10px">
                            <div id="loading" ng-show="loading">
                                <img src="spinner.gif">
                            </div>
                                <table ng-show="!loading"  class="table table-striped table-bordered table-hover tabel-condensed">
                                    <thead>
                                        <tr>
                                            <td>Key</td>
                                            <td>Priority</td>
                                            <td>Summary</td>
                                            <td>Assignee</td>
                                            <td>Status</td>
                                            <td>Due Date</td>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <tr ng-repeat="data in myticketIssuesData">
                                            <td>
                                                <a href={{data.fields.customfield_10023._links.web}} target="_blank">
                                                    {{data.key}}
                                                </a>
                                            </td>
                                            <td>{{data.fields.priority.name}}</td>
                                            <td>{{data.fields.summary}}</td>
                                            <td>{{data.fields.assignee.displayName}}</td>
                                            <td>{{data.fields.status.name}}</td>
                                            <td>{{data.fields.duedate}}</td>
                                        </tr>
                                    </tbody>
                                    <tfoot>
                                        <tr>
                                            <td align="center" colspan="6">
                                                <!-- <span class="form-group pull-left page-size form-inline">
                                                    <select id="ddlPageSize" class="form-control control-color"
                                                        ng-model="pageSizeSelected" ng-change="changePageSize()">
                                                        <option value="5">5</option>
                                                        <option value="10">10</option>
                                                        <option value="25">25</option>
                                                        <option value="50">50</option>
                                                    </select>
                                                </span> -->
                                                <div class="pull-right">
                                                    <pagination total-items="totalCount" ng-change="pageChanged()"
                                                        items-per-page="pageSizeSelected" direction-links="true"
                                                        ng-model="pageIndex" max-size="maxSize" class="pagination"
                                                        boundary-links="true" rotate="false" num-pages="numPages">
                                                    </pagination>
                                                    <a style="margin-left: 640px;" class="btn btn-primary">Page: {{pageIndex}} / {{numPages}}</a>
                                                </div>
                                            </td>
                                        </tr>
                                    </tfoot>
                                </table>
                        </div>
                    </div>

                </div>

            </div>
        </div>
</body>

</html>