“¿Saben aquel que diu que van dos componentes y un servicio en Angular y…?”
Así comenzaría Eugenio, uno de los genios del humor de todos los tiempos, este artículo, pero me temo que el objeto del mismo no es contar un chiste sino explicar cómo hacer un sistema de paginación sobre una lista de resultados de una forma sencilla y concisa utilizando Angular.
Imaginaos que tenemos que paginar un listado de tiendas para que el scroll de nuestro portal web no se convierta en infinito…
Paginación en Angular
Nuestro template HTML sería algo como esto:
// angular pagination <div> <h1>Listado de tiendas</h1> </div> <div class="results-container"> <div class="columns"> <div class="column is-one-quarter" *ngFor="let shop of shops"> <div class="ficha"> <div class="foto"> <img src="{{shop.image}}"> </div> <h2 class="shopTitle"> <a href="/detail/{{shop.slug}}" >{{shop.name}}</a> </h2> </div> </div> </div> <!--Aquí viene lo interesante--> <app-pagination (paginaEmitter)="goToPage($event)" [page]="page" [totalPages]="totalPages" [numShops]="numShops"></app-pagination> </div>
Obviando los estilos CSS, que no vienen al caso, en este template no tenemos nada del otro mundo excepto un ngFor para recorrer el listado de tiendas que recibimos en la variable shops.
El componente que vamos a estudiar es el que hemos bautizado como app-pagination y hay que destacar de su firma que necesita como entrada un número de página (atributo page), el total de las páginas a dibujar(atributo totalPages) y el número de tiendas total que tiene nuestra colección (atributo numShops).
Nuestro componente app-pagination tiene a su vez un evento de salida que se llama paginaEmitter y que se ejecutará cada vez que el usuario quiera pasar de página.
Bien, pasemos ahora a la declaración de componente dueño de este template…
En esta ocasión le hemos bautizado, en un derroche de originalidad, como shop-list-component y tendría una estructura tal que ésta:
// paginate angular @Component({ selector: 'app-shop-list', templateUrl: './shop-list.component.html', styleUrls: ['./shop-list.component.scss'] }) export class ShopListComponent implements OnInit { public shops: Array<any> = []; //Listado de tiendas public page: Number = 1; //Número de página en la que estamos. Será 1 la primera vez que se carga el componente public totalPages: Number; //Número total de páginas public numShops: number; //Total de tiendas existentes private numResults: number = 10; constructor(private _shopService: ShopService) { } //Traemos en este hook los negocios de la primera página de nuestra colección ngOnInit() { this.getShopsByPage(this.page); } //Función para pasar de página //Esta función se ejecuta cada vez que se desencadena //un evento sobre el componente hijo (app-pagination) goToPage(page: number){ this.page = page; this.getShopsByPage(page); } //Este método llama al servicio dónde se obtiene el listado de tiendas //Recibe una página concreta getShopsByPage(page: Number) { this._shopService.listShops(page).subscribe(data =>{ this.shops = data; this.numShops = data.length; this.totalPages = Math.round(this.numShops / this.numResults); }); } }
No hacen falta muchas explicaciones extra:
El servicio inyectado con el nombre ShopService es el encargado de traer los datos de la página que especifiquemos y la parte backend del aplicativo (ya sea Node.js, Java, etc.) será la que nos devuelva un listado de objetos JSON para poder plasmarlos en el template que hemos visto antes.
Cada vez que el usuario quiera pasar de página, se gestionará dicho evento desde la función gotToPage y el servicio renovará el listado de tiendas recibidas vía REST.
Por fin llegamos al tercer protagonista de nuestra historia: el app-pagination.
Además de tener un nombre feísimo, el tipo es más simple que el mecanismo de un botijo.
Mirad si no su template:
// pagination angular <div *ngIf="numShops" class="pagination"> <div *ngIf="page > 1" class="pagination__prev"><a (click)="anterior()">Anterior</a></div> <div class="pagination__text"> <p>Página {{page}} de {{totalPages}}</p> <p>{{numShops}} resultados</p> </div> <div *ngIf="page < totalPages" class="pagination__next"><a (click)="siguiente()" >Siguiente</a></div> </div>
Este fragmento sólo dibujará algo en pantalla si existe alguna tienda (fijaos en el ngIf) y además tiene unos controles muy sencillos queevitan que aparezca el enlace a Anterior si estamos en la primera página (*ngIf=”page > 1“) o el de Siguiente si hemos llegado a la última (*ngIf=”page <totalPages“).
Los eventos click del ratón se controlan con los métodos anterior() y siguiente() que ahora mismo veremos en la definición del propio componente:
@Component({ selector: 'app-pagination', templateUrl: './pagination.component.html', styleUrls: ['./pagination.component.scss'] }) export class PaginationComponent implements OnInit { @Input() private page: number; @Input() private totalPages: number; @Input() public numShops: number; @Output() paginaEmitter: EventEmitter<number> = new EventEmitter(); constructor() { } ngOnInit() { } siguiente(){ this.page++; this.pasarPagina(); } anterior(){ this.page--; this.pasarPagina(); } pasarPagina(){ this.paginaEmitter.emit(this.page); } }
¡Bien! anterior y siguiente son las funciones que incrementan o decrementan el número de página en la que estamos para después llamar al método verdaderamente interesante de nuestro componente: pasarPagina.
Este método se apoya en el objeto paginaEmitter para emitir un evento que el padre de app-pagination (en este caso ShopListComponent) pueda escuchar y así ejecute a continuación el cambio de página.
¡Nada! hemos llegado al final casi sin esfuerzo… No sabemos si a Eugenio le hubiera hecho gracia esta explicación, pero aquí se despide el duelo.
¡Hasta la próxima!
Por: Javier Lindo
Ingeniero Técnico de Informática y Sistemas (UCM) Experto desarrollador backend en Java, enfocado al mundo JS y sus frameworks, hablando de todo ello en su blog personal mrviriato.es
Exalumno del KeepCoding Bootcamp de Desarrollo Web Si tienes algo que deseas compartir o quieres formar parte de KeepCoding, escríbenos a [email protected].