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?
This question is related to
angularjs
pagination
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"
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>
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
};
}
}
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.
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) ">
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>
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 _x000D_
//just for demonstration purposes only_x000D_
var modelData = [{_x000D_
text: 'Test1'_x000D_
}, {_x000D_
text: 'Test2'_x000D_
}, {_x000D_
text: 'Test3'_x000D_
}];_x000D_
_x000D_
(function(util) {_x000D_
_x000D_
util.PAGE_SIZE = 10;_x000D_
_x000D_
util.range = function(start, end) {_x000D_
var rng = [];_x000D_
_x000D_
if (!end) {_x000D_
end = start;_x000D_
start = 0;_x000D_
}_x000D_
_x000D_
for (var i = start; i < end; i++)_x000D_
rng.push(i);_x000D_
_x000D_
return rng;_x000D_
};_x000D_
_x000D_
util.Pager = function(data) {_x000D_
var self = this,_x000D_
_size = util.PAGE_SIZE;;_x000D_
_x000D_
self.current = 0;_x000D_
_x000D_
self.content = function(index) {_x000D_
var start = index * self.size,_x000D_
end = (index * self.size + self.size) > data.length ? data.length : (index * self.size + self.size);_x000D_
_x000D_
return data.slice(start, end);_x000D_
};_x000D_
_x000D_
self.next = function() {_x000D_
if (!self.canPage('Next')) return;_x000D_
self.current++;_x000D_
};_x000D_
_x000D_
self.prev = function() {_x000D_
if (!self.canPage('Prev')) return;_x000D_
self.current--;_x000D_
};_x000D_
_x000D_
self.canPage = function(dir) {_x000D_
if (dir === 'Next') return self.current < self.count - 1;_x000D_
if (dir === 'Prev') return self.current > 0;_x000D_
return false;_x000D_
};_x000D_
_x000D_
self.list = function() {_x000D_
var start, end;_x000D_
start = self.current < 5 ? 0 : self.current - 5;_x000D_
end = self.count - self.current < 5 ? self.count : self.current + 5;_x000D_
return Util.range(start, end);_x000D_
};_x000D_
_x000D_
Object.defineProperty(self, 'size', {_x000D_
configurable: false,_x000D_
enumerable: false,_x000D_
get: function() {_x000D_
return _size;_x000D_
},_x000D_
set: function(val) {_x000D_
_size = val || _size;_x000D_
}_x000D_
});_x000D_
_x000D_
Object.defineProperty(self, 'count', {_x000D_
configurable: false,_x000D_
enumerable: false,_x000D_
get: function() {_x000D_
return Math.ceil(data.length / self.size);_x000D_
}_x000D_
});_x000D_
};_x000D_
_x000D_
})(window.Util = window.Util || {});_x000D_
_x000D_
(function(ns) {_x000D_
ns.SampleController = function($scope, $window) {_x000D_
$scope.ModelData = modelData;_x000D_
//instantiate pager with array (i.e. our model)_x000D_
$scope.pages = new $window.Util.Pager($scope.ModelData);_x000D_
};_x000D_
})(window.Controllers = window.Controllers || {});
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>_x000D_
<table ng-controller="Controllers.SampleController">_x000D_
<thead>_x000D_
<tr>_x000D_
<th>_x000D_
Col1_x000D_
</th>_x000D_
</tr>_x000D_
</thead>_x000D_
<tbody>_x000D_
<tr ng-repeat="item in pages.content(pages.current)" title="{{item.text}}">_x000D_
<td ng-bind-template="{{item.text}}"></td>_x000D_
</tr>_x000D_
</tbody>_x000D_
<tfoot>_x000D_
<tr>_x000D_
<td colspan="4">_x000D_
<a href="#" ng-click="pages.prev()">«</a>_x000D_
<a href="#" ng-repeat="n in pages.list()" ng-click="pages.current = n" style="margin: 0 2px;">{{n + 1}}</a>_x000D_
<a href="#" ng-click="pages.next()">»</a>_x000D_
</td>_x000D_
</tr>_x000D_
</tfoot>_x000D_
</table>
_x000D_
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="‹"
next-text="›"
first-text="«"
last-text="»">
</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
I just made a JSFiddle that show pagination + search + order by on each column using btford code: http://jsfiddle.net/SAWsA/11/
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;
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/
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:
items
to the pagination links.ng-repeat
, so you can use any expression that could be validly used in an ng-repeat
, including filtering, ordering etc.pagination-controls
directive does not need to know anything about the context in which the paginate
directive is called.For those who are looking for a "plug and play" solution, I think you'll find this useful.
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/
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);
};
});
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();
}]);
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});
}
};
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>
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
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.
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="#">«</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="#">»</a></li>
</ul >
It is useful
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="‹"
data-next-text="›"
data-first-text="«"
data-last-text="»">
</ul>
Source: Stackoverflow.com