I have written an Angular component for pagination. It works like this:
page count < 12
- all pages are shown
Example:
< 1 2 *3* 4 5 6 >
page count >= 12
- first and last pages are shown
- "..." is used for hidden pages
- the current page is centered
- around the center 3 more pages are shown on the left and right
Example:
< 1 … 46 47 48 *49* 50 51 52 … 100 >
paginator.component.ts
import { Component, OnChanges, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-paginator',
templateUrl: './paginator.component.html',
styleUrls: ['./paginator.component.less']
})
export class PaginatorComponent implements OnChanges {
/**
* Inputs:
* numberOfItems: the number of items that will be paginated
* itemsPerPage: the number of items displayed on one page
* currentPage: the page the user has currently selected
* loop: make it possible to loop through pages using prev/ next buttons
*/
@Input() numberOfItems: number;
@Input() itemsPerPage: number;
@Input() currentPage: number = 1;
@Input() loop: boolean = false;
// emits the current page number
@Output() pageChange: EventEmitter<number> = new EventEmitter<number>();
/**
* number of pages is calculated by number of items and items per page
* pages is an array that holds the actual values for the pagination
*/
numberOfPages: number = 0;
pages: Array<number> = [];
constructor() { }
// when input changes, update the pagination
ngOnChanges() {
this.numberOfPages = Math.ceil(this.numberOfItems / this.itemsPerPage);
this.updatePages();
if (this.currentPage > this.numberOfPages) {
this.goToPage(1);
}
}
// fills the pages array with the actual numbers
private updatePages(): void {
// if there are no pages create an emtpy array
if (this.numberOfPages === 0) {
this.pages = [];
return;
}
// if all pages fit into the view simply create an array
if (this.numberOfPages < 12) {
this.pages = Array.from({length: this.numberOfPages}, (v, k) => ++k);
return;
}
// if there are more pages then fit into the view
this.pages = [];
// add first page
this.pages.push(1);
// get start value for the elements in the middle
const start = this.currentPage < 6 ? 2 : this.currentPage > this.numberOfPages - 5 ? this.numberOfPages - 9 : this.currentPage - 4;
const end = start + 9;
// loop through all items in the middle (except first and last page)
for (let i = start; i < end; ++i) {
// if the second or second to last page are "not connected", add `null` instead of the page nmber
// null will be replaced with '...' in the template
if (
(i === start && this.currentPage > 6) ||
(i === end - 1 && this.currentPage < this.numberOfPages - 5)
) {
this.pages.push(null);
continue;
}
this.pages.push(i);
}
// add last page
this.pages.push(this.numberOfPages);
}
onPageChange(): void {
this.updatePages();
this.pageChange.emit(this.currentPage);
}
// goes directly to a page number
goToPage(page: number): void {
this.currentPage = page;
this.onPageChange();
}
// goes to the previous page
goToPrevPage(): void {
const next = this.currentPage - 1;
if (next > 0) {
this.goToPage(next);
return;
}
if (this.loop === false) {
return;
}
this.goToPage(this.numberOfPages);
}
// goes to the next page
goToNextPage(): void {
const next = this.currentPage + 1;
if (next <= this.numberOfPages) {
this.goToPage(next);
return;
}
if (this.loop === false) {
return;
}
this.goToPage(1);
}
}
paginator.component.html
<ul *ngIf="numberOfPages > 0">
<li>
<a [ngClass]="{'disabled': numberOfPages === 1 || (loop === false && currentPage === 1)}" (click)="goToPrevPage()"><</a>
</li>
<li *ngFor="let page of pages">
<a [ngClass]="{'active': currentPage === page} (click)="goToPage(page)" *ngIf="page; else placeholder">
{{ page }}
</a>
<ng-template #placeholder>...</ng-template>
</li>
<li>
<a [ngClass]="{'disabled': numberOfPages === 1 || (loop === false && currentPage === numberOfPages)}" (click)="goToNextPage()">></a>
</li>
</ul>
Example of how to use it
<app-paginator
[numberOfItems]="127"
[itemsPerPage]="10"
[(currentPage)]="1"
(pageChange)="onPageChange()"
></app-paginator>
My concerns
… are mostly about the updatePages()
method. Is it too complex? How can I optimize the use of all these magic numbers? For example, later I want to make it easily adjustable to say "show current plus 1
or 2
elements left and right to the current page" instead if the fixed 3
items currently.
But I'm also looking for answers to these:
Is the performance of this code good? Are variable and method names clear? Should I combine the goToPrevPage()
and goToNextPage()
methods?
updatePages()
, which can help in simplifying the logic. \$\endgroup\$