สารบัญเนื้อหา
- ลักษณะการทำ Paging ใน ASP.NET Core
- เพิ่ม PagedList Helper Class
- ปรับแต่งตัวช่วยในการทำ Paging (Helper)
- Implement pagination ใน API
- สร้าง Pagination ใน Client App (SPA)
- ใช้งาน ngx-bootstrap pagination module
1. ลักษณะการทำ Paging ใน ASP.NET Core
- เป็นการช่วยเพิ่ม Performance เนื่องจากไม่ต้องโหลดข้อมูลทั้งหมดทีเดียว โดยโหลดข้อมูลทีละหน้าเท่านั้น
- ใช้การส่งผ่านค่าพารามิเตอร์ผ่าน Query String เช่น http://localhost:5000/api/users?pageNumber=1&pageSize=5
- จำนวนต่อหน้า (Page Size) ต้องถูกกำหนดไว้ไม่ให้มากเกินไป
- ควรจะมีผลลัพธ์ปรากฏออกมาทุก ๆ หน้า
- สร้าง Query Command แล้วเก็บไว้ในตัวแปร (Variable)
- ทำการ Query แบบ Async
- สร้างผลลัพธ์จากการ Query และจัดเก็บไว้ใน IQueryable<T>
- มีลักษณะเป็น Singleton queries
2. เพิ่ม PagedList Helper Class
สร้าง PagedList.cs ใน Helpers
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace SocialApp_API.Helpers
{
public class PagedList<T> : List<T>
{
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public PagedList(List<T> items, int count, int pageNumber, int pageSize)
{
TotalCount = count;
PageSize = pageSize;
CurrentPage = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageNumber, pageSize);
}
}
}
3. ปรับแต่งตัวช่วยในการทำ Paging (Helper)
สร้าง PaginationHeader.cs ใน Helpers
namespace SocialApp_API.Helpers
{
public class PaginationHeader
{
public int CurrentPage { get; set; }
public int ItemsPerPage { get; set; }
public int TotalItems { get; set; }
public int TotalPages { get; set; }
public PaginationHeader(int currentPage, int itemsPerPage, int totalItems, int totalPages)
{
this.CurrentPage = currentPage;
this.ItemsPerPage = itemsPerPage;
this.TotalItems = totalItems;
this.TotalPages = totalPages;
}
}
}
เพิ่ม AddPagination ใน Extensions.cs
public static void AddPagination(this HttpResponse response, int currentPage, int itemsPerPage, int totalItems, int totalPages)
{
var paginationHeader = new PaginationHeader(currentPage, itemsPerPage, totalItems, totalPages);
var camelCaseFormatter = new JsonSerializerSettings();
camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
response.Headers.Add("Pagination", JsonConvert.SerializeObject(paginationHeader, camelCaseFormatter));
response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
}
สร้าง UserParams.cs ใน Helpers
namespace SocialApp_API.Helpers
{
public class UserParams
{
private const int MaxPageSize = 50;
public int PageNumber { get; set; } = 1;
private int pageSize = 10;
public int PageSize
{
get { return pageSize; }
set { pageSize = (value > MaxPageSize) ? MaxPageSize : value; }
}
}
}
4. Implement pagination ใน API
ปรับปรุง GetUsers ใน ISocialRepository.cs
Task<PagedList<User>> GetUsers(UserParams userParams);
ปรับปรุง GetUsers ใน SocialRepository.cs
public async Task<PagedList<User>> GetUsers(UserParams userParams)
{
var users = _context.Users.Include(p => p.Photos);
return await PagedList<User>.CreateAsync(users, userParams.PageNumber, userParams.PageSize);
}
ปรับปรุง GetUsers ใน UsersControllers.cs
public async Task<IActionResult> GetUsers([FromQuery]UserParams userParams)
{
var users = await _repo.GetUsers(userParams);
var usersToReturn = _mapper.Map<IEnumerable<UserForListDto>>(users);
Response.AddPagination(users.CurrentPage, users.PageSize, users.TotalCount, users.TotalPages);
return Ok(usersToReturn);
}
ลองรันและทดสอบเรียกไปที่
http://localhost:5000/api/users
http://localhost:5000/api/users?pageNumber=2&pageSize=5
5. สร้าง Pagination ใน Client App (SPA)
สร้าง Interface pagination.ts ใน _models
export interface Pagination {
currentPage: number;
itemsPerPage: number;
totalItems: number;
totalPages: number;
}
export class PaginatedResult<T> {
result: T;
pagination: Pagination;
}
ปรับปรุง getUsers() ใน user.service.ts
getUsers(page?, itemsPerPage?): Observable<PaginatedResult<User[]>> {
const paginatedResult: PaginatedResult<User[]> = new PaginatedResult<User[]>();
let params = new HttpParams();
if (page != null && itemsPerPage != null) {
params = params.append('pageNumber', page);
params = params.append('pageSize', itemsPerPage);
}
return this.http.get<User[]>(this.baseUrl + 'users', { observe: 'response', params })
.pipe(
map(response => {
paginatedResult.result = response.body;
if (response.headers.get('Pagination') != null) {
paginatedResult.pagination = JSON.parse(response.headers.get('pagination'));
}
return paginatedResult;
})
);
}
ปรับปรุง member-list.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, Router } from '@angular/router';
import { User } from '../_models/user';
import { UserService } from '../_services/user.service';
import { AlertifyService } from '../_services/alertify.service';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class MemberListResolver implements Resolve<User[]> {
pageNumber = 1;
pageSize = 5;
constructor(private userService: UserService,private router: Router, private alertify: AlertifyService) {}
resolve(route: ActivatedRouteSnapshot): Observable<User[]> {
return this.userService.getUsers(this.pageNumber, this.pageSize).pipe(
catchError(error => {
this.alertify.error('Problem retrieving data');
this.router.navigate(['/home']);
return of(null);
})
);
}
}
ปรับปรุง member-list.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from '../../_models/user';
import { UserService } from '../../_services/user.service';
import { AlertifyService } from '../../_services/alertify.service';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-member-list',
templateUrl: './member-list.component.html',
styleUrls: ['./member-list.component.css']
})
export class MemberListComponent implements OnInit {
users: User[];
constructor(private userService: UserService, private alertify: AlertifyService, private route: ActivatedRoute) { }
ngOnInit() {
this.route.data.subscribe(data => {
this.users = data.users.result;
});
}
// loadUsers() {
// this.userService.getUsers().subscribe((users: User[]) => {
// this.users = users;
// }, error => {
// this.alertify.error(error);
// });
// }
}
ลองรัน http://localhost:4200 (อย่าลืม logout และ login ใหม่)
จะพบว่าข้อมูลสมาชิกมา 5 รายการแรก (หน้าที่ 1) ตามที่กำหนดแล้ว
6. ใช้งาน ngx-bootstrap pagination module
เราจะใช้ ngx bootstrap ในการทำ paging รายละเอียดเพิ่มเติม https://valor-software.com/ngx-bootstrap/#/pagination
ทำการ imports ใน app.module.ts
PaginationModule.forRoot()
import { PaginationModule } from 'ngx-bootstrap/pagination';
ปรับปรุง member-list.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from '../../_models/user';
import { UserService } from '../../_services/user.service';
import { AlertifyService } from '../../_services/alertify.service';
import { ActivatedRoute } from '@angular/router';
import { Pagination, PaginatedResult } from 'src/app/_models/pagination';
@Component({
selector: 'app-member-list',
templateUrl: './member-list.component.html',
styleUrls: ['./member-list.component.css']
})
export class MemberListComponent implements OnInit {
users: User[];
pagination: Pagination;
constructor(private userService: UserService, private alertify: AlertifyService, private route: ActivatedRoute) { }
ngOnInit() {
this.route.data.subscribe(data => {
this.users = data.users.result;
this.pagination = data.users.pagination;
});
}
pageChanged(event: any): void {
this.pagination.currentPage = event.page;
this.loadUsers();
}
loadUsers() {
this.userService
.getUsers(this.pagination.currentPage, this.pagination.itemsPerPage)
.subscribe((res: PaginatedResult<User[]>) => {
this.users = res.result;
this.pagination = res.pagination;
}, error => {
this.alertify.error(error);
});
}
}
ปรับปรุง member-list.component.html
<div class="container mt-5">
<div class="row">
<div *ngFor="let user of users" class="col-lg-2 col-md-3 col-sm-6">
<app-member-card [user]="user"></app-member-card>
</div>
</div>
</div>
<div class="d-flex justify-content-center">
<pagination [boundaryLinks]="true"
[totalItems]="pagination.totalItems"
[itemsPerPage]="pagination.itemsPerPage"
[(ngModel)]="pagination.currentPage"
(pageChanged)="pageChanged($event)"
previousText="‹" nextText="›" firstText="«" lastText="»">
</pagination>
</div>
ทดสอบเปลี่ยนหน้าดู