ben tedder : code things

Create a pagination component in Angular 4

Don't spend time looking for pre-built packages. It's super simple to create your own pagination component in Angular 4. (If you want to skip the tutorial and go straight to the bottom to see the full gist/source code, scroooooll on down.)

Step 1. Create a basic component

I said this was simple, let's create a simple component to begin with.

// pagination.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-pagination',
  templateUrl: './pagination.component.html',
  styleUrls: ['./pagination.component.css']
})
export class PaginationComponent {

  constructor() {}

}
<!-- pagination.component.html -->
<div class="pagination">

</div>
/* pagination.component.scss */
.pagination {
}

Step 2. Figure out the Inputs and Outputs of the component

So, we have a basic angular component, but that doesn't do us much good. We need it to do stuff. Let's create a few inputs for this component. First, we need to know how many total items we're paginating through. Then we need to know what page we're on. Then we need to know how many items per page are being shown. We also need to know how many pages to show between the "previous" and "next" buttons. Finally we need some way of disabling the pagination while content is loading, so we'll have a loading input. Here's what that all looks like:

// pagination.component.ts
import { Component, Input } from '@angular/core';

...

export class PaginationComponent {
  @Input() page: number; // the current page
  @Input() count: number; // how many total items there are in all pages
  @Input() perPage: number; // how many items we want to show per page
  @Input() pagesToShow: number; // how many pages between next/prev
  @Input() loading: boolean; // check if content is being loaded

  constructor() {}

  ...

}

We also need to have some callbacks when a user clicks the previous/next buttons or a page number. Even though we haven't created those DOM elements yet, we can assume they'll come.

// pagination.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';

...

export class PaginationComponent {
  
  // @Input() xyz
  ...

  @Output() goPrev = new EventEmitter<boolean>();
  @Output() goNext = new EventEmitter<boolean>();
  @Output() goPage = new EventEmitter<number>();

  constructor() {}

  ...

}

So now when we use our component it should look like this:

<!-- list.component.html -->
<pagination
  [count]="someCountVar"
  [page]="somePageVar"
  [perPage]="somePerPageVar"
  [pagesToShow]="5"
  [loading]="someLoadingVar"
  (onPrev)="prevPage()"
  (onNext)="nextPage()"
  (onPage)="goToPage($event)">
</pagination>

Step 3. Fill out the pagination component's html template

Now let's create the placeholders for all the numbers in our pagination component. I want it to look something like this:

page 23 of 40 --- Prev | 21 22 23 24 25 | Next --- 256 messages

In order to do this I'm going to reference several methods in this view that we'll create in the next step. I realize that it's not best practice to stick these methods in the view, and that I should probably generate just the values when the component hears the changes. But that's not the point of this tutorial, you can clean it up on your own.

<!-- pagination.component.html -->
<div class="pagination" *ngIf="count > 0">
  <span>{{ getMin() }} of {{ getMax() }} of {{ count }}</span>
  <span>{{ totalPages() }} pages</span>
  <button (click)="onPrev()" [disabled]="page === 1 || loading">Previous</button>
  <button *ngFor="let pageNum of getPages()" (click)="onPage(pageNum)"></button>
  <button (click)="onNext()" [disabled]="lastPage() || loading">Next</button>
</div>

That's all the template needs! Of course, you should make it look good with CSS, do whatever you want. Now for the fun part, all the basic math we need to calculate all those things. I'm going to paste the full text of my pagination.component.ts file here so you can see it all together.

// pagination.component.ts
import { Component, Input, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'my-pagination',
  templateUrl: './pagination.component.html',
  styleUrls: ['./pagination.component.scss']
})
export class PaginationComponent {
  @Input() page: number;
  @Input() count: number;
  @Input() perPage: number;
  @Input() loading: boolean;
  @Input() pagesToShow: number;

  @Output() goPrev = new EventEmitter<boolean>();
  @Output() goNext = new EventEmitter<boolean>();
  @Output() goPage = new EventEmitter<number>();

  constructor() { }

  getMin(): number {
    return ((this.perPage * this.page) - this.perPage) + 1;
  }

  getMax(): number {
    let max = this.perPage * this.page;
    if (max > this.count) {
      max = this.count;
    }
    return max;
  }

  onPage(n: number): void {
    this.goPage.emit(n);
  }

  onPrev(): void {
    this.goPrev.emit(true);
  }

  onNext(next: boolean): void {
    this.goNext.emit(next);
  }

  totalPages(): number {
    return Math.ceil(this.count / this.perPage) || 0;
  }

  lastPage(): boolean {
    return this.perPage * this.page > this.count;
  }

  getPages(): number[] {
    const c = Math.ceil(this.count / this.perPage);
    const p = this.page || 1;
    const pagesToShow = this.pagesToShow || 9;
    const pages: number[] = [];
    pages.push(p);
    const times = pagesToShow - 1;
    for (let i = 0; i < times; i++) {
      if (pages.length < pagesToShow) {
        if (Math.min.apply(null, pages) > 1) {
          pages.push(Math.min.apply(null, pages) - 1);
        }
      }
      if (pages.length < pagesToShow) {
        if (Math.max.apply(null, pages) < c) {
          pages.push(Math.max.apply(null, pages) + 1);
        }
      }
    }
    pages.sort((a, b) => a - b);
    return pages;
  }
}

Now, this is really all you need for your pagination to work. The only other things you'll need to add are the variables you pass in to the pagination component (ie, current page, total items, page size, pages to show, etc). You manage the "current page" in your main component, just like you call your services to get the next page of info. The only thing you really pass the pagination component is numbers. You manage all the numbers in your main component. Hopefully that's clear? I'll post a gist that has everything together. I'll go ahead and include the CSS I use in case you care. I'll also include a basic way to call the pagination and handle creating the variables: