Como fazer paging em AngularJS?
Tenho um conjunto de dados de cerca de 1000 itens em memória e estou a tentar criar um pager para este conjunto de dados, mas não sei como fazer isto.
estou a usar uma função de filtro personalizada para filtrar os resultados, e isso funciona bem, mas de alguma forma preciso de obter o número de páginas.
Alguma pista?21 answers
Directiva de paginação Angular da interface
Check out UI Bootstrap's directiva de paginação. Acabei por usá-lo ao invés do que é postado aqui, pois tem características suficientes para o meu uso atual e tem um espectrómetro de teste completo para acompanhá-lo.
Ver
<!-- table here -->
<pagination
ng-model="currentPage"
total-items="todos.length"
max-size="maxSize"
boundary-links="true">
</pagination>
<!-- items/page select here if you like -->
Controlador
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);
});
});
Eu fiz um mergulhador de trabalho para referência.
Legado Version:
Ver
<!-- 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 -->
Controlador
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);
});
});
Eu fiz um mergulhador de trabalho para referência.
Implementei recentemente um paging para o local construído com o Angular. Você chan checar a fonte: https://github.com/angular/builtwith.angularjs.org
Eu evitaria usar um filtro para separar as páginas. Você deve dividir os itens em páginas dentro do controlador.<ul>
<li dir-paginate="item in items | itemsPerPage: 10">{{ item }}</li>
</ul>
// then somewhere else on the page ....
<dir-pagination-controls></dir-pagination-controls>
É isso. Tem as seguintes características:
- não é necessário um código personalizado no seu controlador para ligar a Colecção
items
às ligações de paginação.
Não estás obrigado a usar uma mesa. ou gridview - você pode paginar qualquer coisa que você pode ng-repetir!
- delegados em
ng-repeat
, para que possa usar qualquer expressão que possa ser validamente usada numng-repeat
, incluindo filtragem, ordenação, etc. - funciona entre os controladores - a Directiva
pagination-controls
não precisa de saber nada sobre o contexto em que a Directivapaginate
é chamada.
Demo: http://plnkr.co/edit/Wtkv71LIqUR4OhzhgpqL?p=preview
Para aqueles que procuram um "plug and play" solução, acho que vai achar isto útil.Código
O código está disponível aqui no GitHub e inclui um bom conjunto de testes:
Https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination
Se você está interessado eu também escrevi uma pequena peça com um pouco mais de visão sobre o projeto do módulo: http://www.michaelbromley.co.uk/blog/108/paginate-almost-anything-in-angularjs/
Acabei de fazer um JSFiddle que mostra paginação + procura + ordem em cada coluna usando o código btford: http://jsfiddle.net/SAWsA/11/
Eu atualizei Scotty.NET's plunkr http://plnkr.co/edit/FUeWwDu0XzO51lyLAEIA?p=preview, de modo que ele usa as versões mais recentes angular, angular-ui, e bootstrap.
Controlador
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();
};
});
Componente IU de inicialização
<pagination boundary-links="true"
max-size="3"
items-per-page="itemsPerPage"
total-items="todos.length"
ng-model="currentPage"
ng-change="pageChanged()"></pagination>
Esta é uma solução javascript pura que eu embrulhei como um serviço Angular para implementar a lógica de paginação como nos resultados de pesquisa do google.
Demonstração de trabalho no CodePen at http://codepen.io/cornflourblue/pen/KVeaQL/
Detalhes e explicações em este post no blog
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
};
}
}
//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()">«</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()">»</a>
</td>
</tr>
</tfoot>
</table>
O adaptador angular móvel jQuery tem um filtro de chamada de pessoas do qual se pode basear.
Aqui está um violino demo que o usa (adicione mais de 5 itens e ele fica pagado): http://jsfiddle.net/tigbro/Du2DY/
Aqui está a fonte: https://github.com/tigbro/jquery-mobile-angular-adapter/blob/master/src/main/webapp/utils/paging.js
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>
Abaixo da solução bastante simples.
<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) ">
<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" >
Nos vossos angulares:
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);
};
});
Paginação Ng-repetição
<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>
Desde o Angular 1, 4, o filtro limitTo
também aceita um segundo argumento opcionalbegin
{{limitto_ expression / limitTo: limit: begin}}
Comecem. (opcional) texto / número
Indice a partir do qual começar a limitação. Como um índice negativo, begin indica um deslocamento do fim da entrada. O valor por omissão é 0.
Então você não precisa criar uma nova diretiva , Este argumento pode ser usado para definir a compensação da paginação
ng-repeat="item in vm.items| limitTo: vm.itemsPerPage: (vm.currentPage-1)*vm.itemsPerPage"
As mensagens anteriores recomendavam basicamente como construir um paging sozinho. Se você é como eu, e prefere uma diretriz acabada, eu acabei de encontrar uma grande Chamada ngTable . Suporta ordenação, filtragem e paginação.
É uma solução muito limpa, tudo o que precisa na sua opinião:
<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>
E no controlador:
$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));
}
});
Ligação ao GitHub: https://github.com/esvit/ng-table/
$scope.pagination = {total: null, pages: [], config: {count: 10, page: 1, size: 7}};
Lógica para paginação:
/*
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);
});
E a minha opinião sobre o 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 >
É útil
É uma escolha maravilhosa
([1]) uma directiva destinada a facilitar a comunicação de grandes conjuntos de dados, exigindo simultaneamente o mínimo de informações reais sobre as chamadas de pessoas. Estamos muito dependentes do servidor para "filtrar" resultados neste esquema de paging. A idéia central é que nós só queremos manter a "página" ativa de itens-em vez de manter toda a lista de itens na memória e paging no lado do cliente.
O que eu achei ser uma solução fácil e pequena para paginação é combinar uma directiva com um filtro que usa as mesmas variáveis de âmbito.
Para implementar isto, você adiciona o filtro no array e adiciona o directiv como este
<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 e p_Step são variáveis de âmbito que podem ser personalizadas na scope else the default value of the p_Size is 5 and p_Step is 1.
Quando um passo é alterado na paginação, o p_ Step é actualizado e irá despoletar uma nova filtragem pelo filtro cust_pagination. O filtro cust_pagination então corta a lista, dependendo do valor do p_Step, como em baixo, e só devolve os registos activos seleccionados na secção de paginação
var startIndex = nStep * nPageSize;
var endIndex = startIndex + nPageSize;
var arr = items.slice(startIndex, endIndex);
return arr;
Demonstração ver a solução completa neste mergulhador
Scotty.NET a resposta do ' S e a reedição do user2176745 para versões posteriores são ambos grandes, mas ambos perdem algo que a minha versão do AngularJS (v1.3.15) quebra em:
Eu não estou definido em $scope.makeTodos.
Como tal, a substituição por esta função corrige-a para versões angulares mais recentes.
$scope.makeTodos = function() {
var i;
$scope.todos = [];
for (i=1;i<=1000;i++) {
$scope.todos.push({ text:'todo '+i, done:false});
}
};
Panorâmica geral : paginação utilizando
- ng-repeat
- uib-pagination
Ver :
<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>
Controlador JS :
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();
}]);
Você pode facilmente fazer isso usando a diretriz Bootstrap UI.
Esta resposta é uma modificação da resposta dada por @Scotty.NET, alterei o código porque a Directiva<pagination>
está agora desactualizada.
O seguinte código gera a paginação:
<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>
Para o tornar funcional, use isto no seu controlador:
$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();
Consulte esta página para mais opções de paginação: Directiva de Paginação de Iniciação
Eu gostaria de adicionar a minha solução que funciona com ngRepeat
e filtros que você usa com ele sem usar um $watch
ou uma matriz cortada.
Os resultados dos filtros serão paginados!
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;
};
}]);
No HTML certifique - se que aplica os seus filtros ao ngRepeat
antes de o filtro de paginação.
<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>