Angular 8 Pagination and Sorting
by Angular Material
เตรียมความพร้อม
บทความนี้ต้องการ backend ซึ่งได้อธิบายไว้แล้วที่นี่
เขื่อว่าเพื่อนๆที่เข้ามาอ่านบทความนี้คงเคยเขียน angular มาบ้างแล้ว ดังนั้นผมจะขอข้ามขั้นตอนการติดตั้ง Node.js และ Angular CLI ไปนะครับ
สร้างโปรเจกต์
ng new bookdb-front
- เลือก SCSS
- ไม่เลือก Routing
เพิ่ม Angular Material
ng add @angular/material
- เลือก theme ตามใจชอบ ผมได้เลือก purple-green
แล้วไปกำหนดค่า theme ในไฟล์ styles.scss
@import '@angular/material/prebuilt-themes/purple-green.css';...
ทดสอบเพิ่ม MatToolbarModule ในไฟล์ app.module.ts
import { MatToolbarModule } from '@angular/material/toolbar';...imports: [...MatToolbarModule...],
จากนั้นเพิ่ม <mat-toolbar> ในไฟล์ app.component.html ส่วนแท็กอื่นๆลบทิ้งให้หมด
<mat-toolbar color="primary">Welcome to bookdb</mat-toolbar>
หากทุกอย่างเรียบร้อยดีต้องได้แบบนี้
ที่เหลือเป็นการแสดงหาความรู้ด้วยตนเองเกี่ยวกับ Angular Material เข้าไปอ่าน เข้าไปลองหยิบมาใช้งาน เชื่อว่าจะชื่นชอบ theme ตัวนี้ครับ
Angular Material: Table
ถัดมาเราต้องการ Table เพื่อแสดงรายการหนังสือ จะต้องเพิ่ม MatTableModule เข้าไปในไฟล์ app.module.ts
import { MatTableModule } from '@angular/material/table';...imports: [...MatToolbarModule,
MatTableModule...],
เปิดไฟล์ app.component.ts หัวใจของการใช้ table ก็คือ
displayedColumns = ['isbn','name','authors','year','pages'];dataSource = new MatTableDataSource<TableData>();
displayColumns พูดว่าจะให้ แสดง/ซ่อน column ใดบ้างตามลำดับ รายชื่อ column ที่อยู่ในนี้คือแสดงทั้งหมด โดยที่ชื่อ column ที่ต้องการให้แสดงจะต้องสามารถหยิบหามาจาก TableData หรือจะสร้างขึ้นเองก็ได้-สำคัญคือต้องอ้างอิงถึงได้
dataSource พูดว่าค่าของ table คืออะไร สามารถให้ค่าเป็น array ของ data ได้ ตัวอย่างเช่น [1, ‘AAA’, 10.5, true, {}] แต่ในที่นี้ผมให้ค่าเป็น MatTableDataSource ซึ่งจะต้อง include เข้ามา
import { MatTableDataSource } from '@angular/material/table';
MatTableDataSource เราสามารถกำหนด T ของสิ่งที่ต้องการให้มันสนใจได้ ในที่นี้เลือกเป็น interface ชื่อ TableData และนี่คือหน้าตาของมัน
interface TableData { isbn: string; name: string; authors: string; year: string; pages: number;}
หากถามว่า MatTableDataSource เนี่ยต้องกำหนด T ตลอดไหม คำตอบคือ ไม่ เราอาจใส่ T หรือ any หรือไม่ใส่อะไรเลยก็ได้ ตัวอย่าง
dataSource = new MatTableDataSource();
T หมายถึง class หรือ interface ใดๆที่ต้องการระบุเฉพาะเจาะจง เพื่อให้ compiler ตรวจสอบความถูกต้องให้ก่อน runtime
เปิดไฟล์ app.component.html เราจะต้องวางแท็ก <table> เพื่อกำหนดโครงสร้างของตาราง
<table>...</table>
จากนั้นระบุ dataSource ให้กับมัน (เพราะชื่อตัวแปรมันเหมือนกัน ตัวหนาคือ dataSource ที่เราเพิ่งทำความรู้จักไปก่อนหน้านี้)
<table mat-table [dataSource]="dataSource">
เพื่อนๆจะเห็นว่า <table> นี่ก็แท็ก HTML ธรรมดา แต่ที่มันสามารถเข้าใจ [dataSource] ได้ก็เพราะว่ามันถูกจัดการด้วย MatTableModule
เอาล่ะใส่ style ให้มันด้วย จะได้สวยๆ
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
อีกนิดเรื่อง style ใส่ table ความกว้าง 100% เพื่อให้มันได้ยืดเต็มจอ ด้วยการเปิดไฟล์ app.component.scss แล้วเพิ่ม
table { width: 100%;}
ต่อไปเป็นเรื่องของการแสดง column และข้อมูลภายใน ประกอบไปด้วยสองส่วนสำคัญได้แก่
- ส่วนของ loop
- ส่วนของการแสดง data
ส่วนของ loop
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr><tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
ส่วนของการแสดง data
<ng-container matColumnDef="isbn"> <th mat-header-cell *matHeaderCellDef mat-sort-header>ISBN</th> <td mat-cell *matCellDef="let element"> {{element.isbn}} </td></ng-container>
โดยที่ทั้งสองส่วนนี้จะต้องอยู่ภายใน <table> … </table>
จากตัวอย่างของส่วนที่แสดง data อันดับแรก matColumnDef จะสื่อถึงชื่อ column ที่อยู่ในตัวแปร displayedColumns ที่เราได้กล่าวไปแล้วในตอนต้น มันจะต้องเขียนเหมือนกัน ไม่เช่นนั้นข้อมูลจะไม่แสดงและอาจก่อให้เกิด error
อันดับที่สอง element.isbn ตรงนี้ element คือตัวแปรที่ประกาศขึ้นมา (ใช้ชื่ออื่นก็ได้) เพื่อรับเอาค่าที่ถูก loop มาให้ในแต่ละรอบ ส่วน .isbn ก็คือชื่อ property ที่อยู่ใน T หรือก็คือ interface TableData ของ dataSource นั่นเองครับ
ดึงข้อมูลมาแสดงผล
เรามี backend แล้วแล้วก็มี RESTful APIs ที่สามารถดึงข้อมูลมาแสดงผลได้ดังนี้
localhost:8080/books
เริ่มด้วยการเขียน service ไปเรียกข้อมูล สร้าง folder ชื่อ services และไฟล์ชื่อ book.service.ts
src/app/services/book.service.ts
จากนั้นเรียกมาใช้ที่ไฟล์ app.component.ts
แล้วปรับปรุงหน้า app.component.html เป็นดังนี้
เพื่อนจะสังเกตเห็นว่ามี Page กับ Book models เพิ่มขึ้นมา ให้สร้าง folder ชื่อ models ด้านในประกอบด้วยไฟล์ page.ts และ book.ts
src/app/models/page.ts
src/app/models/book.ts
มีรายละเอียดแต่ละไฟล์ดังนี้
ไฟล์ page.ts
ไฟล์ book.ts
อย่าลืมว่าเราต้องการ proxy.conf.json ให้สร้างไว้ภายใน src folder เพื่อจะได้เรียก API ระหว่าง port 4200 กับ port 8080 ภายใต้ domain localhost เดียวกันนี้ได้
src/proxy.conf.json
ไฟล์ proxy.conf.json
จากนั้นรันได้แล้วครับ โดยกำหนด proxy ไปด้วย
ng serve --proxy-config proxy.conf.json
ผลลัพธ์
Angular Material: Paginator
มาถึงตัวพระของเราสักทีนะครับ นั่นคือการทำ pagination สำหรับ Angular Material นี้เขาก็ได้เตรียม Paginator component ให้ใช้ ใช้ดีมาก
มาถึงตรงนี้เพื่อนๆก็คงคุ้นเคยไฟล์ต่างๆเป็นอย่างดีแล้ว เช่นเคยเราต้องการ module แล้วก็ <mat-paginator>
เปิดไฟล์ app.module.ts เพิ่ม
import { MatPaginatorModule } from '@angular/material/paginator';...imports: [...MatToolbarModule,
MatTableModule,
MatPaginatorModule...],
ไฟล์ app.component.html เพิ่ม <mat-paginator> ไว้ท้ายสุด
<mat-paginator [length]="length" [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" (page)="changePage($event)"></mat-paginator>
รายละเอียดของ attributes ข้างต้นมีดังต่อไปนี้
- length คือจำนวนข้อมูลทั้งหมด เช่น 20
- pageSize คือจำนวนข้อมูลต่อหน้า เช่น 5
- pageSizeOptions คือช่วงของข้อมูลที่ต้องการให้แสดง มีผลต่อ pageSize เช่น [5, 10, 30, 50]
- page เป็น output ที่ส่ง event ออกมา ในที่นี้รับด้วยเมธอด changePage แล้วนำ event ที่ส่งออกมานั้นใส่เป็น argument
ทีนี้เปิดไฟล์ app.component.ts เลยจ้า
- ประกาศตัวแปรเพิ่ม
- กำหนดค่า length จาก API
- เมธอด changePage นำ PageEvent เข้ามา มี e.pageIndex ทำให้รู้ค่า offset ว่าอยู่หน้าไหน และ e.pageSize เปรียบเสมือน limit ให้เลือกข้อมูลเป็นจำนวนเท่านั้นตัวตามที่ได้กำหนดไว้
ประกาศตัวแปรเพิ่ม
length = 0;pageSize = 5;pageSizeOptions: number[] = [5, 10];
กำหนดค่า length จาก API
เมธอด changePage
ผลลัพธ์
ลองกำหนด pageSize ใหม่จากการเลือกตรง Items per page เป็น 10
ลองกดเครื่องหมาย > ก็จะเรียกมาอีก 10 ข้อมูล
ยัง ยังไม่จุใจ จริงๆ Book มันมี id แต่เราไม่เลือกนำมาแสดงด้วย (อยากซ่อน id ไว้) ในที่นี่เราต้องการลำดับของข้อมูลที่สามารถคำนวณได้ตาม page ที่เปลี่ยนไป
เพิ่มลำดับ
การเพิ่มลำดับนี้ก็คือการสร้าง column ใหม่ จะให้ชื่อว่า no โดยกำหนดลงไปใน displayedColumns
displayedColumns = ['no','isbn','name','authors','year','pages'];
จากนั้นสร้างอีกหนึ่ง column ชื่อ No ไว้ใน tempalte ด้วย
<ng-container matColumnDef="no"><th mat-header-cell *matHeaderCellDef mat-sort-header>No</th><td mat-cell *matCellDef="let element; let i = index">{{no + (i + 1)}}</td></ng-container>
แล้วกลับไปที่ app.component.ts กำหนดค่าเริ่มต้นให้ตัวแปร no เท่ากับศูนย์
no = 0;
ไปดูผลลัพธ์
ความตลกคือกดเครื่องหมาย > หรือ < แล้ว No ค่าของมันไม่เปลี่ยนตาม แบบนี้ก็ต้องสร้าง logic เพิ่มในเมธอด changePage ถูกต้องไหม
งดงาม
Angular Material: Sort Header
มาถึงอย่างสุดท้ายของบทความนี้แล้ว ก็คือการเรียงลำดับข้อมูล จาก มากไปหาน้อย หรือ น้อยไปหามาก ด้วยการกดที่หัว column
Angular Material จัดการเรื่องนี้ไว้แล้ว
ง่ายมาก หัวใจสำคัญมีอยู่แค่ 2 อย่าง
- ควบคุม table ด้วย matSort
- กำหนด mat-sort-header ไว้ในส่วนของ th ซึ่งเป็นหัวของ column ที่ต้องการให้ sort ได้
เริ่มเหมือนเดิม import module ก่อน
import { MatSortModule } from '@angular/material/sort';...imports: [...MatToolbarModule,
MatTableModule,
MatPaginatorModule,
MatSortModule...],
จากนั้นปรับปรุง template ให้มี matSort และ mat-sort-header เชิญชม
ทีนี้ไปเพิ่มการผูก MatSort เข้ากับ dataSource ของ table โดยเริ่มจากประกาศตัวแปรชื่อ sort มีค่าเป็น ref ที่ผูกกับ template ไว้
@ViewChild(MatSort, { static: true }) sort: MatSort;
จากนั้นที่เมธอด ngOnInit ก็ว่า
this.dataSource.sort = this.sort;
เสร็จสิ้น
ดูผลลัพธ์
สรุป
จะเห็นได้ว่าเมื่อเราใช้ framework เช่น Angular และ Angular Material งานของเราก็จะเสร็จไวขึ้นโดยที่ตัว framework รับประกันความถูกต้องให้ตามวิธีการที่มันกำหนดไว้ งานเร็วสวยงามได้คุณภาพ
ปรบมือหน่อยนะถ้าคิดว่านี่เป็นประโยชน์