Build Angular Pagination Without a Library
Build Angular Pagination Without a Library

In this post we will implement such module inside Angular as pagination.

Finished project

This is how our project will look like. So it doesn't make a lot of sense to try and find the perfect library if you can implement this code in less than 10 minutes.

Architecture

So here I already generated for us an empty Angular project. There is nothing inside just an empty app.component.

First of all we must talk about our pagination component and how we want to structure it. Actually for this component I will create a separate module because essentially this is a separate feature. Inside this module I will put our component and then later we can use it for example in our app.component.

So how we are intending to use it?

<pagination
  [currentPage]="currentPage"
  [limit]="20"
  [total]="500"
  (changePage)="changePage($event)"
></pagination>

Here we defined all input of our pagination component. We need currentPage, limit, total and a callback to change the page. Which means our component will be completely dump and won't do anything except of giving changePage event back. It won't even store currentPage inside.

export class AppComponent {
  currentPage = 1;

  changePage(page: number): void {
    this.currentPage = page;
  }
}

Here we added currentPage inside our app.component and changePage function which simply updates currentPage property.

Basic module

Now it's time to create our module and component inside.

// src/app/pagination/pagination.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { PaginationComponent } from './components/pagination/pagination.component';

@NgModule({
  imports: [CommonModule],
  exports: [PaginationComponent],
  declarations: [PaginationComponent],
})
export class PaginationModule {}

Here we created our module for pagination and provided a component inside which we will create in a second.

// src/app/pagination/components/pagination/pagination.component.ts
@Component({
  selector: 'pagination',
  templateUrl: './pagination.component.html',
  styleUrls: ['./pagination.component.css'],
})
export class PaginationComponent {
  @Input() currentPage: number = 1;
  @Input() total: number = 0;
  @Input() limit: number = 20;
  @Output() changePage = new EventEmitter<number>();
}

As you can see we added our pagination component with correct inputs.

It is important to default all our inputs to avoid Typescript error.

We even defaulted total to zero to avoid bugs in our component.

The last step what we need to do it to import this component inside app.module.ts.

@NgModule({
  ...
  imports: [
    ...
    PaginationModule,
  ],
})
export class AppModule {}

Module

We just rendered here a word pagination but it means a lot because we don't have any errors and we successfully binded our module and component together.

Getting pages

Now we must get a total pages count. We can easily calculate it because we have both total and limit. Then we can use pages count to generate an array of pages that we want to render.

export class PaginationComponent implements OnInit {
  @Input() currentPage: number = 1;
  @Input() total: number = 0;
  @Input() limit: number = 20;
  @Output() changePage = new EventEmitter<number>();

  ngOnInit(): void {
    const pagesCount = Math.ceil(this.total / this.limit);
    console.log(pagesCount)
  }
}

As you can see in browser we get 25 pages which is correct. Most importantly we upped pages count to bigger number which means if we have not 500 element but 501 we want to create 26 pages and not 25.

Now it is time to generate an array of pages. This is simply an array of number from 1 to pagesCount. But there is no function inside javascript to generate such array this is why we must create our own function.

export class PaginationComponent implements OnInit {
  @Input() currentPage: number = 1;
  @Input() total: number = 0;
  @Input() limit: number = 20;
  @Output() changePage = new EventEmitter<number>();

  pages: number[] = [];

  ngOnInit(): void {
    const pagesCount = Math.ceil(this.total / this.limit);
    this.pages = this.range(1, pagesCount);
  }

  range(start: number, end: number): number[] {
    return [...Array(end).keys()].map((el) => el + start);
  }
}

Here we created a range function which generated for us an array of numbers from one number to another. We called this function on initialize to generate an array of pages.

Range

Markup

Now let's add markup for our component.

<ul class="pagination">
  <li
    *ngFor="let page of pages"
    [ngClass]="{ 'page-item': true, active: currentPage === page }"
    (click)="changePage.emit(page)"
  >
    <span class="page-link">{{ page }}</span>
  </li>
</ul>

Here we render our list of pages. We also apply page-item class to each of them and active class only when currentPage is a page in loop. Also we added click event where we call out emit to outside and provide inside clicked page.

But our project is not styled at all. And actually I prepared styles for the whole pagination feature. You can just copy them from the source code under the post.

// src/app/pagination/components/pagination/pagination.component.css
.pagination {
  display: inline-block;
  padding-left: 0;
  margin-top: 1rem;
  margin-bottom: 1rem;
  border-radius: 0.25rem;
}
.page-item {
  display: inline;
}
.page-item:first-child .page-link {
  margin-left: 0;
  border-bottom-left-radius: 0.25rem;
  border-top-left-radius: 0.25rem;
}
.page-item:last-child .page-link {
  border-bottom-right-radius: 0.25rem;
  border-top-right-radius: 0.25rem;
}
.page-item.active .page-link,
.page-item.active .page-link:focus,
.page-item.active .page-link:hover {
  z-index: 2;
  color: #fff;
  cursor: default;
  background-color: #5cb85c;
  border-color: #5cb85c;
}
.page-item.disabled .page-link,
.page-item.disabled .page-link:focus,
.page-item.disabled .page-link:hover {
  color: #818a91;
  pointer-events: none;
  cursor: not-allowed;
  background-color: #fff;
  border-color: #ddd;
}
.page-link {
  position: relative;
  float: left;
  padding: 0.5rem 0.75rem;
  margin-left: -1px;
  color: #5cb85c;
  text-decoration: none;
  background-color: #fff;
  border: 1px solid #ddd;
  cursor: pointer;
}
.page-link:focus,
.page-link:hover {
  color: #3d8b3d;
  background-color: #eceeef;
  border-color: #ddd;
}

Finished project

As you can see in browser we successfully implemented our pagination feature without any libraries.

And you might say now that it is a basic pagination and we render all pages at once. We don't have previous/next links or first page/last page. You are totally right but the pagination that we built can be enough for lots of websites.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done