Angular ngx-pagination 구현하기
Angular ngx-pagination을 이용하여 페이징을 구현하는 방법에 대해 알아보겠습니다. ngx-pagination은 페이징 처리를 위해 만들어진 Angular 라이브러리입니다. Angular5+ 이상에서 동작하며 보다 자세한 내용과 live demo는 아래 링크를 통해 확인할 수 있습니다.
구현 환경은 다음의 버전을 기준으로 구성하였습니다.
- Node.js v12.13.1
- MySQL v5.7.3
- Angular v11.2.4
구현에 앞서 다음 포스팅 내용을 참고하여 서버와 애플리케이션의 구성이 필요합니다.
1. Paging 모듈 설치
npm을 이용하여 ngx-pagination을 설치해줍니다.
$ npm ngx-pagination
2. Module 설정
src/app/tutorial/tutorial.module.ts 파일에 ngx-pagination 모듈을 다음과 같이 추가해줍니다.
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { NgxPaginationModule } from 'ngx-pagination';
import { TutorialListComponent } from 'src/app/tutorial/tutorial-list/tutorial-list.component';
import { TutorialDetailComponent } from 'src/app/tutorial/tutorial-detail/tutorial-detail.component';
import { TutorialAddComponent } from 'src/app/tutorial/tutorial-add/tutorial-add.component';
import { TutorialService } from 'src/app/tutorial/tutorial.service';
@NgModule({
imports: [
FormsModule,
ReactiveFormsModule,
CommonModule,
NgxPaginationModule,
RouterModule.forChild([
{
path: '',
redirectTo: 'tutorial/list',
pathMatch: 'full'
},
{
path: 'tutorial/list',
component: TutorialListComponent
},
{
path: 'tutorial/add',
component: TutorialAddComponent
},
{
path: 'tutorial/:id',
component: TutorialDetailComponent
}
])
],
declarations: [
TutorialListComponent,
TutorialDetailComponent,
TutorialAddComponent
],
providers: [
TutorialService
]
})
export class TutorialModule {
}
3. Model 설정
src/app/common/value/common.value.ts 파일에 Page 클래스를 다음과 같이 작성해줍니다.
export class Page {
public itemsPerPage: number; // 페이지당 목록 개수
public currentPage: number; // 현재 페이지
public totalItems: number; // 전체 목록 개수
constructor() {
this.itemsPerPage = 0;
this.currentPage = 0;
this.totalItems = 0;
}
}
4. Component 설정
src/app/tutorial/tutorial-list/tutorial-list.component.ts 파일을 다음과 같이 작성해줍니다. 페이징에 관련된 변수와 메서드들을 추가해줍니다.
import { Component, OnInit } from '@angular/core';
import { Tutorial } from 'src/app/tutorial/tutorial.value';
import { TutorialService } from 'src/app/tutorial/tutorial.service';
import { SelectValue, Page } from 'src/app/common/value/common.value';
@Component({
selector: 'app-tutorial-list',
templateUrl: './tutorial-list.component.html',
styleUrls: []
})
export class TutorialListComponent implements OnInit {
public tutorialList: Tutorial[] = [];
public tutorial: Tutorial = new Tutorial();
public page: Page = new Page();
public keyword = '';
public pageSizeList: number[] = [5, 10, 50, 100];
constructor(private tutorialService: TutorialService) {}
ngOnInit(): void {
this.initPage();
this.retrieveTutorialList();
}
// Initialize page
public initPage(): void {
this.page.currentPage = 1;
this.page.itemsPerPage = this.pageSizeList[0];
} // func - initPage
// Retrieve all tutorials
public retrieveTutorialList(): void {
this.tutorialService.getTutorialList(this.keyword, this.page)
.subscribe(response => {
if (response) {
this.tutorialList = response.rows;
this.page.totalItems = response.count;
} else {
console.log(response);
}
});
} // func - retrieveTutorialList
// Change page
public changePage(event: number): void {
this.page.currentPage = event;
this.retrieveTutorialList();
} // func - pageChanged
// Change page size
public changePageSize(event: any): void {
this.page.itemsPerPage = event.target.value;
this.page.currentPage = 1;
this.retrieveTutorialList();
} // func - changePageSize
}
view 템플릿 설정을 위해 src/app/tutorial/tutorial-list/tutorial-list.component.html 파일에 <pagination-controls> component 및 페이징 동작에 필요한 다른 부분들을 작성해줍니다.
<div class="list row">
<!-- Search:S-->
<div class="col-md-8">
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Please enter keyword" [(ngModel)]="keyword"/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" (click)="retrieveTutorialList()">Search</button>
</div>
</div>
</div>
<!-- Search:E -->
<!-- Page Size:S -->
<div class="col-md-8">
<div class="px-1 py-1 mb-1">
<div class="container d-flex flex-wrap justify-content-start">
<h4>Tutorial List</h4>
<div class="btn text-dark me-2">
Items Per Page:
</div>
<div class="d-flex bd-highlight mb-2">
<div class="bd-highlight">
<select class="custom-select" (change)="changePageSize($event)" >
<option *ngFor="let pageSize of pageSizeList">
{{pageSize.value}}
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Page Size:E -->
<!-- List:S -->
<div class="col-md-8">
<ul class="list-group">
<li class="list-group-item"
*ngFor="let tutorial of tutorialList | paginate: page; let idx = index;"
routerLink="/tutorial/{{tutorial.id}}">
{{page.totalItems - ((page.currentPage-1) * page.itemsPerPage) - idx}}. {{tutorial.title}}
</li>
</ul>
</div>
<!-- List:E -->
<!-- Pagination:S -->
<div class="col-md-8">
<div class="px-1 py-3 mb-1">
<div class="container d-flex flex-wrap justify-content-center">
<pagination-controls
[directionLinks]="true"
[responsive]="true"
[previousLabel]="'Prev'"
[nextLabel]="'Next'"
[maxSize]="10"
(pageChange)="changePage($event)">
</pagination-controls>
</div>
</div>
</div>
<!-- Pagination:E -->
</div>
NgFor 디렉티브 내의 리스트에 대해 PaginatePipe(paginate)를 사용하여 page 객체를 전달합니다. PaginatePipe에는PaginationInstance의 인터페이스에 맞는 객체를 인자로 전달해야 합니다. 위의 코드처럼 page 객체를 사용하지 않고 PaginatePipe를 사용하고자하는 경우엔 다음과 같이 작성해주면 됩니다.
<element *ngFor="let item of collection | paginate: {
id: 'foo',
itemsPerPage: pageSize,
currentPage: p,
totalItems: total
}">
</element>
PaginatePipe에 사용할 수 있는 프로퍼티는 다음과 같습니다.
- id: 1개 이상의 pagination 인스턴스를 사용할 경우 필요한 값. (사용시 PaginationControls 컴포넌트의 id 값과 동일하게 설정)
- itemsPerPage: 각 페이지에 표시할 목록 개수
- currentPage: 현재 페이지
- totalItems: 전체 목록 개수
<pagination-controls> 컴포넌트는 페이징을 디스플레이하기위해 사용합니다. Foundation 프레임워크의 pagination 컴포넌트를 기반으로 템플릿이 구현되어 있습니다. 여기에 사용할 수 있는 프로퍼티는 다음과 같습니다.
- id: 1개 이상의 pagination 인스턴스를 사용할 경우 필요한 값. (사용시 PaginatePipe의 id 값과 동일하게 설정)
- directionLinks: previousLabel, nextLabel 표시 여부 (default true)
- responsive: 작은 화면의 경우에 대한 개별 페이지 표시 여부 (default false)
- previousLabel: 다음 링크 라벨
- nextLabel: 이전 링크 라벨
- maxSize: 표시할 최대 페이지 링크 개수
- autoHide: 전체 목록 개수가 1페이지를 넘지 않을 경우에 대한 pagination 표시 여부 (default false)
- screenReaderPaginationLabel: 웹접근성을 위한 screenreader pagination 라벨
- screenReaderPageLabel: 웹접근성을 위한 screenreader 페이지 라벨
- screenReaderCurrentLabel: 웹접근성을 위한 screenreader 현재 페이지 라벨
- (pageChange): 개별 페이지에 대한 클릭 이벤트 핸들러
- (pageBoundsCorrection): 현재 페이지가 범위를 벗어난 경우에 대한 이벤트 핸들러
5. Server-side Pagination 설정
위의 컴포넌트까지만 구성하고 서버와 애플리케이션을 실행해서 확인해봐도 페이징이 정상적으로 동작하는 것을 확인할 수 있습니다. 하지만 페이징을 이용하여 목록을 조회해보면 화면에서만 페이징이 처리되고 server-side에서는 다음과 같이 전체 목록이 모두 조회되는 문제가 발생합니다.
이와 같은 문제를 해결하기 위해 server-side pagination을 구현해줍니다. src/app/tutorial/tutorial.service.ts 내에 작성한 getTutorialList() 함수를 다음과 같이 수정해줍니다.
public getTutorialList(keyword: string, page: Page): Observable<any> {
return this.http.get<Tutorial[]>(`${baseUrl}?keyword=${keyword}&page=${page.currentPage-1}&size=${page.itemsPerPage}`);
}
url의 querystring이 변경되었기 때문에 서버 파일을 수정해줍니다. 앞서 구성한 mysql-server에서 app/controller/controller.ts 내의 findAll 함수를 다음과 같이 수정해줍니다.
exports.findAll = (req, res) => {
const keyword = req.query.keyword ? req.query.keyword : '';
const limit = parseInt( (req.query.size ? req.query.size : DEFAULT_PAGE_SIZE).toString() );
const offset = parseInt( (req.query.page ? req.query.page * limit : 0).toString() );
let condition = { where: {}, limit: limit, offset: offset };
if (keyword) {
condition = {
where : {
[Op.or]: [
{
title: {
[Op.like]: `%${keyword}%`
}
},
{
description: {
[Op.like]: `%${keyword}%`
}
}
]
},
limit: limit,
offset: offset
}
};
Tutorial
.findAndCountAll(condition)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message: err.message || 'Retrieve all tutorials failure.'
});
});
};
6. 실행 및 테스트
6.1. MySQL Server 실행
앞서 구성한 MySQL Server를 Node.js로 실행해줍니다.
$ node mysql-server.ts
6.2. Angular Application 실행
npm install을 실행하고 application을 실행해줍니다.
$ npm install
$ npm start
브라우저에서 http://localhost:4200 url로 접속하면 다음과 같은 화면이 나타나는 것을 확인할 수 있습니다.
6.3. 페이징 테스트
브라우저에서 페이징 동작을 확인해줍니다.
6.3.1. Page Size 변경
6.3.2. 검색 & 페이징
6.3.4. Server-side Pagination
server-side pagination이 가능하도록 수정한 이후에는 페이지당 목록 개수별로 조회되는 것을 확인할 수 있습니다.
이상으로 Angular ngx-pagination을 이용하여 페이징을 구현하는 방법에 대해 알아봤습니다.
※ Reference
- github.com, Pagination for Angular, github.com/michaelbromley/ngx-pagination
- bezkoder.com, Angular 11 Pagination example with ngx-pagination, bezkoder.com/angular-11-pagination-ngx/
- bezkoder.com, Server side Pagination in Node.js with Sequelize & MySQL, bezkoder.com/node-js-sequelize-pagination-mysql/
- npmjs.com, ngx-pagination, www.npmjs.com/package/ngx-pagination
- sequelize.org, Model, sequelize.org/master/class/lib/model.js~Model.html#static-method-findAndCountAll