DEV Community

Cover image for Creating custom Angular 17 pagination component
John Doe
John Doe

Posted on

Creating custom Angular 17 pagination component

Data listing is one of the crucial points in any web application, be it a very small sample of a complicated comprehensive enterprise application. Primarily we list data in tables, which will definitely require sorting, paging (pagination), search within the table. This post deals with implementing custom pagination for an Angular 17 application.

tl;dr (The title says it all, then why to make it long. Let's get to work.)

Let's start with the pagination component, which will have a pagination.component.ts file and a pagination.component.html file.

pagination.component.ts



/*
 * @config       : input configuration file
 * @fetchData    : event triggered on clicking buttons in the pagination component
 * perPageCount  : how many rows to show in the table; choose from a list
 * pagination    : controls pagination  
*/    
@Input() config: any;
@Output() fetchData = new EventEmitter();

perPageCount: number;
pagination: any = {};

/*
 * OnInit, initialize the 'pagination' variable
 * Use 'dataChannel' if you intent to call web service from within this component
 * Use 'searchCriteria' and 'sortConfig' when this component will be expanded so as to include the listing too within this component
 * 'searchCriteria' and 'sortConfig' doesn't come under the scope of this post
*/
initPager() 
{
     this.perPageCount = 10;

     this.pagination.totalRecords = this.config.totalRecords || 0;
     this.pagination.page = this.config.CurrentPage || 1;
     this.pagination.perPage = this.config.perPage || 10;

     this.pagination.dataChannel = this.config.dataChannel || null;

     this.pagination.range = { limit: 10, start: 0, end: 0 };
     this.pagination.searchCriteria = this.config.searchCriteria || '';
     this.pagination.sortConfig = this.config.sortConfig;

     this.pagination.from = '';
     this.pagination.to = '';
     this.pagination.total = '';

     this.pagination.totalPages = Math.ceil(this.config.totalRecords / this.config.perPage) || 0;

     this.generateLabel();
     this.calculateRange();
     this.buildRange();
}


/*
 * On change of the number of rows to display drop down,
 * set the perPage value and the emit the config to the parent component
*/
changePerPage(index: number) 
{
     this.pagination.perPage = 'Read the method description';
     this.fetchData.emit(this.pagination);
}


/*
 * Generate label - In the end, it will look like 'Showing 1 - 10 of 100 records
 * Calculate 'start' and 'end' with values of 'currentPage' and 'perPage'
*/
generateLabel() 
{
     var start = 'Read the method description',
         end = start + 'Read the method description';

     this.pagination.from = start;
     this.pagination.to = end;
     this.pagination.total = this.pagination.totalRecords;
}


/*
 * Goto Page : go to the page number passed
*/
gotoPage(page: number) 
{
     this.pagination.page = page;
     this.pagination = Object.assign({}, this.pagination);
     this.fetchData.emit(this.pagination);
}


/*
 * Navigate to next page
*/
gotoNext() 
{
     if (this.pagination.page + 1 <= this.pagination.totalPages) {
        this.pagination.page = this.pagination.page + 1;
        this.fetchData.emit(this.pagination);
     }
}

/*
 * Navigate to previous page
*/
gotoPrevious() 
{
     if (this.pagination.page - 1 > 0) 
     {
         this.pagination.page = this.pagination.page - 1;
         this.fetchData.emit(this.pagination);
     }
}


/*
 * Navigate to first page
*/
gotoFirst() 
{
     this.pagination.page = 1;
     this.fetchData.emit(this.pagination);
}


/*
 * Navigate to last page
*/
gotoLast() 
{
     this.pagination.page = Math.ceil(this.pagination.totalRecords / this.pagination.perPage);
     this.fetchData.emit(this.pagination);
}


/*
 * Find the start and end of pagination
 * Logic : Let there be a variable named 'range' which refers to the number of 
 *         pages that should be shown in the pagination. The first item in the 
           list i.e, 'start' should be current page (minus) range divided by 2, 
           as we need to show equal numbers on both sides.The last item in the 
           list i.e, 'end' should be current page (plus) range divided by 2, 
           as we need to show equal numbers on both sides.


*/
calculateRange() 
{
     var start = 'Read the method description',
         end = 'Read the method description';

     this.pagination.range.start = start;
     this.pagination.range.end = end;
}


/*
 * Build range to show in pagination buttons
*/
range = [];
buildRange() 
{
     this.range = [];
     for (var i = this.pagination.range.start; i <= this.pagination.range.end; i++) 
     {
         this.range.push(i);
     }
}


Enter fullscreen mode Exit fullscreen mode

pagination.component.html



<div *ngIf="pagination.totalRecords>0">
    <div class="col-xs-6">
        <div role="status" aria-live="polite">
            Showing <span class="txt-color-darken">{{pagination.from}}</span> to <span class="txt-color-darken">{{pagination.to}}</span> of <span class="text-primary">{{pagination.total}}</span> entries
        </div>
    </div>
    <div class="col-xs-6">
        <div>
            <ul class="pagination pagination-sm">
                <li [ngClass]="{disabled: pagination.page <= 1}">
                    <a href="javascript: void(0)" (click)="gotoFirst()"><span>&laquo;</span></a>
                </li>
                <li [ngClass]="{disabled: pagination.page <= 1}">
                    <a href="javascript: void(0)" (click)="gotoPrevious()"><span>&lsaquo;</span></a>
                </li>
                <li *ngFor="let pp of range" [ngClass]="{active: pp == pagination.page}">
                    <a href="javascript: void(0)" (click)="gotoPage(pp)">{{pp}}</a>
                </li>
                <li [ngClass]="{disabled: pagination.page == pagination.totalPages}">
                    <a href="javascript: void(0)" (click)="gotoNext()"><span>&rsaquo;</span></a>
                </li>
                <li [ngClass]="{disabled: pagination.page == pagination.totalPages}">
                    <a href="javascript: void(0)" (click)="gotoLast()"><span>&raquo;</span></a>
                </li>
            </ul>
        </div>
    </div>
</div>


Enter fullscreen mode Exit fullscreen mode

Now, the main component in which we will use this component.

main.component.ts



/*
 * @config       : pagination configuration file
*/    
config: any;

perPageCount: number;
pagination: any = {};

/*
 * OnInit, initialize the 'config' variable and call the web service
 * When the pagination sub component updates the config object, call the
 * initConfig method again (EventEmitter).
 * 'sortConfig'   : used for sorting
 * searchCriteria : used for searching
*/
initConfig(e)
{
     if (e != null && e.page > 0) 
     {
         this.config = {
             CurrentPage: e.page,
             perPage: e.perPage,
             searchCriteria: e.searchCriteria,
             totalRecords: e.totalRecords,
             //sortConfig: {
             //    sortField: e.sortField,
             //    sortOrder: e.sortOrder
             //}
         };
     }
     else 
     {
         this.config = {
             CurrentPage: 1,
             perPage: 10,
             searchCriteria: '',
             totalRecords: 0,
             //sortConfig: {
             //    sortField: 'createdDate',
             //    sortOrder: 'desc'
             //}
         };
     }

     this.getData();
}


getData()
{
     this.myService.post(data_url, this.config)
         .subscribe(
         data => 
         {
             // Update the config file with perPage, currentPage 
             // and totalRecords
         },
         error => 
         {
             // Log error
         });
}


Enter fullscreen mode Exit fullscreen mode

Coming to the template file of the main component.

main.component.html



<table>
    <thead>
    </thead>
    <tbody>
        <tr>
            ... stub ...
        </tr>
    </tbody>
</table>   

<rg-pagination [config]="config" (fetchData)="initConfig($event)"></rg-pagination>


Enter fullscreen mode Exit fullscreen mode

Points to be noted:

  • What we do is pass the configuration object as an input parameter to the pagination component. The data that is emitted from the pagination component on change is taken as an output parameter, this is essentially the initConfig method being updated and then calling the web service.
  • This code is a stub. Searching and sorting, and calling the web service from within the pagination component doesn't come under the scope of this example.

Top comments (0)