1
\$\begingroup\$

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()">&lt;</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()">&gt;</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?

\$\endgroup\$
2
  • \$\begingroup\$ Can you please add comments where ever possible to help us understand your intentions(especially in updatePages(), which can help in simplifying the logic. \$\endgroup\$
    – shaktimaan
    Nov 6, 2019 at 10:48
  • 1
    \$\begingroup\$ @shaktimaan Thanks for your feedback. I already updated the question. Hopefully, this is helpful. \$\endgroup\$
    – lampshade
    Nov 6, 2019 at 11:07

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.