Paginación en una tabla con AngularJS 1.x
Con la directiva ng-repeat de AngularJS podemos fácilmente mostrar una lista de elementos en forma de tabla, sin embargo, se nos puede volver problema cuando tenemos muchos registros para mostrar en dicha tabla. Nuestra página podría crecer mucho hacia abajo y el usuario tendría que recurrir a hacer uso del scroll para ir navegando en los registros (esto le resta puntos a la accesibilidad de nuestra página web).
Para evitar dicho problema podríamos hacer un simple pagineo con nuestra tabla, así se adaptaría dinámicamente a la cantidad de registros que deseamos mostrar.
El objetivo de este post es crear dicho pagineo, paso a paso, y llegar al siguiente resultado:
Puedes ver el ejemplo finalizado y funcionando en JSBIN.
Vamos a iniciar creando la tabla que mostrará nuestros registros (en este ejemplo vamos a crear un tabla con 6 columnas).
<table> <thead> <tr> <th>Id</th> <th>Primer Nombre</th> <th>Segundo Nombre</th> <th>Primer Apellido</th> <th>Segundo Apellido</th> <th>Fecha Nacimiento</th> </tr> </thead> <tbody> <!-- Aquí es donde vamos a agregar nuestros registros --> </tbody> </table>
Para que nuestra tabla se vea mejor, agregaremos un poco de CSS.
table { background-color: #FFF; color: #333; font-family: sans-serif; font-size: .9em; font-weight: 300; text-align: left; line-height: 40px; border-spacing: 0; border: 1px solid #428bca; margin: 20px auto; cursor: pointer; } thead tr:first-child { background: #428bca; color: #fff; border: none; } th { font-weight: bold; padding-left: 10px; padding-right: 10px; } th:first-child, td:first-child { padding: 0 15px 0 20px; } thead tr:last-child th { border-bottom: 2px solid #ddd; } tbody tr:hover { background-color: #f0fbff; } tbody tr:last-child td { border: none; } tbody td { border-bottom: 1px solid #ddd; } td:last-child { text-align: right; padding-right: 10px; }
Con esto tendremos como resultado solo el encabezado de nuestra tabla, puedes ver nuestro avance en JSBIN.
Ahora crearemos un módulo en Angular, que llamaremos DemoPagineo, dicho módulo lo guardaremos en una variable llamada app (para que más adelante podamos agregarle más componentes).
var app = angular.module('DemoPagineo', []);
NOTA: En este punto es necesario que ya tengamos referenciada la librería de AngularJS, a través de la etiqueta script, dentro de la sección head de nuestra página.
Para hacer referencia a nuestro nuevo módulo lo haremos desde la etiqueta body, utilizando la directiva ng-app (propia de AngularJS).
<body ng-app="DemoPagineo">
Vamos a necesitar un controller, el cual llamaremos: miTabla y lo referenciaremos desde un elemento div que contendrá la tabla que ya creamos, de la siguiente manera.
<p ng-controller="miTabla"> <table> ... </table> </p>
El código de nuestro controller será este por el momento:
app.controller('miTabla', ['$scope', function($scope) { console.log("entro a mi controller"); }]);
Si vemos nuestro avance en JSBIN y abrimos la consola de nuestro navegador, podremos ver que que se imprime el mensaje: “entro a mi controller”, ya que es lo único que por el momento lo hemos puesto a hacer.
Dentro de nuestro controller crearemos la variable llamada data, la cual tendrá los registros que deseamos mostrar en nuestra tabla en un formato JSON.
// En esta parte colocaré el objeto JSON con un registro para que no se extienda mucho, si quieren ver todos los registros deberán hecharle un vistazo al código en JSBIN. $scope.data = $scope.usuarios = [{ id: 1, primernombre: 'Juan', segundonombre: 'Mario', primerapellido: 'Pérez', segundoapellido: 'Maldonado', fechanacimiento: '23-12-1985' }, { ... // Aqui van los demás registros }];
Posteriormente agregaremos una fila a la tabla que creamos de primero, utilizando la directiva ng-repeat para que se muestren todos los registros que estén en nuestra variable data, el código HTML quedaría así:
<tr ng-repeat="reg in data"> <td>{{reg.id}}</td> <td>{{reg.primernombre}}</td> <td>{{reg.segundonombre}}</td> <td>{{reg.primerapellido}}</td> <td>{{reg.segundoapellido}}</td> <td>{{reg.fechanacimiento}}</td> </tr>
En este punto al ver nuestra página en JSBIN podemos ver que nos aparecen todos los registros en nuestra tabla y la página crece hacia abajo, entre más registros tengamos más grande nuestra página (esto es lo que deseamos evitar).
Así que ahora agregaremos una serie de botones en la parte inferior de nuestra tabla (los que nos servirán para que el usuario navegue entre las distintas páginas que van a estar disponibles).
<!-- Navegar hacia atrás --> <button type='button' ng-disabled='currentPage == 0' ng-click='currentPage = currentPage - 1'>«</button> <!-- Navegar a una página especifica--> <button type='button' ng-disabled='currentPage == page.no - 1' ng-click='setPage(page.no)' ng-repeat='page in pages'>{{page.no}}</button> <!-- Navegar hacia adelante --> <button type='button' ng-disabled='currentPage >= data.length/pageSize - 1' ng-click='currentPage = currentPage + 1'>»</button>
Dentro de las directivas de estos botones estamos utilizamos la variable currentPage y la función setPage, esto lo tendremos que declarar más adelante dentro de nuestro controller.
También agregamos un poco de css para mejorar el diseño de nuestros botones:
button { position: relative; vertical-align: top; height: 60px; font-size: 22px; color: white; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); background: #3498db; border: 0; border-bottom: 2px solid #2a8bcc; cursor: pointer; -webkit-box-shadow: inset 0 -2px #2a8bcc; box-shadow: inset 0 -2px #2a8bcc; } button:active { top: 1px; outline: none; -webkit-box-shadow: none; box-shadow: none; } button:disabled { outline: none; -webkit-box-shadow: none; box-shadow: none; opacity: 0.5; }
Podemos ver entonces nuestro avance en JSBIN, los botones de pagineo aparecen en la parte inferior, pero los que nos permitirán navegar a una página específica aún no aparecen, ya que nos falta definirlos en nuestro controller.
Regresando a modificar nuestro controller, ahora vamos a agregar las variables: currentPage, pageSize y pages (ésta última será un arreglo que contendrá la cantidad de páginas disponibles).
$scope.currentPage = 0; $scope.pageSize = 10; // Esta la cantidad de registros que deseamos mostrar por página $scope.pages = [];
También agregaremos las funciones: configPages y setPages, las cuales nos servirán para que el controller pueda definir cuantas páginas se van a generar y qué página selecciona el usuario.
$scope.configPages = function() { $scope.pages.length = 0; var ini = $scope.currentPage - 4; var fin = $scope.currentPage + 5; if (ini < 1) { ini = 1; if (Math.ceil($scope.data.length / $scope.pageSize) > 10) fin = 10; else fin = Math.ceil($scope.data.length / $scope.pageSize); } else { if (ini >= Math.ceil($scope.data.length / $scope.pageSize) - 10) { ini = Math.ceil($scope.data.length / $scope.pageSize) - 10; fin = Math.ceil($scope.data.length / $scope.pageSize); } } if (ini < 1) ini = 1; for (var i = ini; i <= fin; i++) { $scope.pages.push({ no: i }); } if ($scope.currentPage >= $scope.pages.length) $scope.currentPage = $scope.pages.length - 1; }; $scope.setPage = function(index) { $scope.currentPage = index - 1; };
Hacemos la llamada a la función configPages() al final de nuestro controller para que se encargue de analizar los registros contenidos en data y defina la cantidad de páginas disponibles:
$scope.configPages();
Si volvemos a ejecutar nuestro código en JSBIN, veremos que ya nos aparecen en la parte inferior la cantidad de páginas que contienen nuestros registros, pero aún muestra todos los registros en la misma página.
Para finalizar entonces agregaremos un filter a nuestro módulo de AngularJS, que llamaremos startFromGrid y que se encargará de truncar los registros de las tablas por cada página (para que no nos aparezcan todos los registros en una misma página).
app.filter('startFromGrid', function() { return function(input, start) { start = +start; return input.slice(start); }; });
La llamada a éste filter la realizaremos desde el ng-repeat que le colocamos a nuestra tabla inicialmente, es decir, cambiar la línea:
<tr ng-repeat="reg in data">
Por la siguiente (coloco en negrita el texto nuevo que fue agregado):
<tr ng-repeat="reg in data | startFromGrid: currentPage * pageSize | limitTo: pageSize">
Ahora si, hemos terminado, podemos ver en JSBIN el resultado final. En nuestro ejemplo se mostrarán 10 registros por cada página pero podemos modificar la variable pageSize si deseamos más o menos registros.
OTROS ENLACES:
- También puedes ver el ejemplo en línea en mi perfil de codepen.
- Si quisieras agregar bootstrap a este ejemplo, basta con quitar el css que nosotros agregamos y referenciar la librería de bootstrap, también coloque en codepen un ejemplo de esto.
- En github se encuentra también se encuentra publicado el código para facilitar las descargas,o si quieres proponer cambios y mejoras:
Recuerda suscribirte si quieres recibir más artículos como éste en tu correo electrónico.
si yo tengo un
ng-repeat
de la siguiente manera:a yo al implementar lo que explicas de la siguiente manera:
me saca error de slice no esta declarado:
app.filter('startFromGrid', function() {
return function(input, start) {
start = +start;
return input.slice(start);
};
});
Como podria corregir esto, dado que al hacer el análisis el error es me sale es por lo siguiente
track by $index
como podria implentar sin que me saque error.
muchas gracias con la ayuda prestada y muy buen aporte.
Según la documentación oficial de Angular: “The default tracking function (which tracks items by their identity) does not allow duplicate items in arrays. This is because when there are duplicates, it is not possible to maintain a one-to-one mapping between collection items and DOM elements.”
Que información tienes en el arreglo que estas usando para hacer el
ng-repeat
? Hay algún registro duplicado? Debes considerar agregarle eltrack by $index
. Puedes ver más información de eso en: ngRepeat – Tracking and Duplicateshola, es un carrito de compras dinamico, con fotos etc…., y necesito el index por producto seleccionado, para la primera pagina funciona perfectamente, aparecen todos los productos y dichas funcionalidades, pero al pasar de pagina cargan bien los productos pero la funcionalidad se pierde
Muy buen aporte, He intentado que el numero de botones esté limitado a 5 por ejemplo, es decir cuando se este en la pagina 4 o 5, aparezca el botón 6 suponiendo que hay mas paginas y que desaparezca el botón 1. No se si podrías aportar algo al respecto o alguna recomendación de como resolverlo?
Buenas, este es el ejemplo que mejor me funciona pero aun así los números de las páginas no me aparecen y no me dan fallos en consola.
¿Qué podría ser?.
Gracias.
Cuál es el error que te da en consola?
no me da errores en consola ni en la vista