[ตอนที่ 14] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การใช้งาน Paging

Line
Facebook
Twitter
Google

สารบัญเนื้อหา

  1. ลักษณะการทำ Paging ใน ASP.NET Core
  2. เพิ่ม PagedList Helper Class
  3. ปรับแต่งตัวช่วยในการทำ Paging (Helper)
  4. Implement pagination ใน API
  5. สร้าง Pagination ใน Client App (SPA)
  6. ใช้งาน 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>

ทดสอบเปลี่ยนหน้าดู

Line
Facebook
Twitter
Google
[ตอนที่ 16] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Sorting
[ตอนที่ 15] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Filtering
[ตอนที่ 14] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การใช้งาน Paging
No Preview
[ตอนที่ 13] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การกำหนดรูปแบบการแสดงผลวัน-เวลา
เตรียม Atom สำหรับ React Native #3
เตรียม Visual Studio Code สำหรับ React Native #2
การติดตั้ง React Native บน macOs #1
การกำหนดค่า TF_MIN_GPU_MULTIPROCESSOR_COUNT เพื่อให้ TensorFlow ใช้งาน GPU ทุกตัว
ติดตั้ง Ubuntu 17.04 ใช้งานร่วมกับ Windows 10
การติดตั้ง TensorFlow & Caffe บน Ubuntu 16.04