Angular PrimeNG CDK與Angular非套件資料分類排序功能實作差異
前言
每一個前端工程師或許都有在專案遇到過這個問題,客戶提出的需求,在新的套件技術中,已經可以輕鬆達成。
像是在Table表格中的欄位,根據使用者點選表格上方進行各欄位由高至低、或由低至高的資料排序。
而使用Angular作為專案中,前端的開發框架,
在資料分類排序需求的套件技術上,往往在既有的專案中,需要更新版本才可以相容。
但偏偏在既有專案運作的功能上,又無法隨意更新,以免舊的功能出現問題。
只好以Angular既有的設計模式去刻出這樣的需求了!
因此,此篇文章將以表格資料排序的需求,來比較Angular UI套件PrimeNG技術,
與既有的Angular設計模式,達成的任務後所需要的程式碼差異作比較。
Why PrimeNG?
在眾多的Angular套件中,不乏完成此需求任務的公開CDK,但為何選擇PrimeNG,作為此技術差異比較探討的套件呢?
跟耳熟能詳的Angular Material比較起來,PrimeNG算是支援Angular的UI設計套件中,相對也是另一個發展成熟的套件。
尤其它標榜精緻的美術設計、高穩定性、易上手以及客製化的使用者介面設計,筆者曾稍微涉獵過,認為進入門檻不高,釋出的CDK功能也相對穩定。
因此,相較於眾所周知的Angular Material,多了幾分神祕感的PrimeNG,似乎更有探討與介紹價值。
透過此篇文章的技術,盼能讓更多開發者知道PrimeNG這款套件,在未來前端網頁開發專案中,多一個選擇的工具。
接下來,我們就開始吧!
資料內容
讓我們先來看看資料長什麼樣子:
[
{
name: 'Kareem Abdul-Jabbar',
total: 38387,
game: 1560,
average: 24.6,
birth: new Date('1947-04-16'),
season: 20,
jersey: 33,
height: 218,
photo: 'Jabbar.jpg'
},
{
name: 'Karl Malone',
total: 36928,
game: 1476,
average: 25.0,
birth: new Date('1963-07-24'),
season: 19,
jersey: 32,
height: 206,
photo: 'MailMan.jpg'
},
{
name: 'LeBron James',
total: 34241,
game: 1265,
average: 27.5,
birth: new Date('1984-12-30'),
season: 17,
jersey: 23,
height: 206,
photo: 'LeBron.jpg'
},
{
name: 'Kobe Bryant',
total: 33643,
game: 1346,
average: 25.0,
birth: new Date('1978-08-23'),
season: 20,
jersey: 24,
height: 198,
photo: 'Kobe.jpeg'
},
{
name: 'Michael Jordan',
total: 32292,
game: 1072,
average: 30.1,
birth: new Date('1963-02-17'),
season: 14,
jersey: 23,
height: 198,
photo: 'Jordan.jpg'
},
{
name: 'Dirk Nowitzki',
total: 31560,
game: 1522,
average: 20.7,
birth: new Date('1978-06-19'),
season: 21,
jersey: 41,
height: 213,
photo: 'Dirk.jpg'
},
{
name: 'Wilt Chamberlain',
total: 31419,
game: 1045,
average: 30.1,
birth: new Date('1936-08-21'),
season: 15,
jersey: 13,
height: 216,
photo: 'Wilt.jpg'
},
{
name: "Shaquille O'Neal",
total: 28596,
game: 1207,
average: 23.7,
birth: new Date('1972-03-06'),
season: 19,
jersey: 34,
height: 216,
photo: 'Shaq.jpg'
},
{
name: "Moses Malone",
total: 27409,
game: 1329,
average: 20.6,
birth: new Date('1955-03-23'),
season: 21,
jersey: 22,
height: 208,
photo: 'Moses.jpeg'
},
{
name: "Elvin Hayes",
total: 27313,
game: 1303,
average: 21.0,
birth: new Date('1945-11-17'),
season: 16,
jersey: 44,
height: 206,
photo: 'Hayes.jpg'
},
]
有在關注NBA的您猜到了嗎?
這是NBA歷史上總得分排行榜前十名的資料列表,計算截止至2020年球季結束。
畫面呈現
不多說,就先來看自己刻的成品效果,畫面呈現長得是什麼樣子:
這是預設的json資料,根據總得分的排名key上去的。
各位應該有看到表格標題旁邊都有的上下箭頭,這是預設尚未進行排序的圖示,
主要提示使用者,此列表擁有根據各欄位標題分類排序的功能。
要怎麼操作呢?
很簡單,只要按下各標題欄位就可以了!
首先,我們先點選姓名欄位:
此時會發現變成往下的箭頭,代表目前是由上往下排序。
因此,名字開頭為D的Dirk Nowitzki,變成了首位,以此類推。
此時,我們再按下欄位標題,表格的資料列表排序又不同了:
此時圖示變成往上的箭頭,由英文字母排序叫後面的W,成為了第一筆資料。
程式碼的部份是怎麼寫的呢?
別急,開箱程式碼之前,讓我們先看看對照組的PrimeNG套件要怎麼安裝。
開始使用PrimeNG
在使用一個套件之前,不外乎必須開啟Angular CLI面板
輸入CLI指令進行套件的安裝,指令碼如下:
npm install primeng --save
npm install primeicons --save
primeng是安裝主套件;
primeicons顧名思義,就是安裝其使用者介面會用到的icon了。
接著,找到專案中的angular.json (或angular-cli.json)這個檔案
搜尋關鍵字 "styles",將PrimeNG會用到的css從node_modules引入,程式碼如下:
"styles": [
"src/styles.scss",
"node_modules/primeng/resources/themes/saga-blue/theme.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/primeicons/primeicons.css"
],
在此先隨機引入一個以藍色為主的視覺色調,名為saga-blue的主題css,
然後準備回去開箱Angular非套件刻出的程式碼。
Angular非套件資料分類排序程式碼
首先看到標題的部份:
除了頭像欄位之外,所有的欄位標題旁邊都有個箭頭符號,表示該欄位可以進行分類排序。
HTML的部份如下:
<thead>
<tr class="text-center">
<th>
Ï 頭像
</th>
<th>
<div (click)="toOrder(0)" style="cursor: pointer;">
<img src="{{ imgArray[0] }}" />
姓名</div>
</th>
<th>
<div (click)="toOrder(1)" style="cursor: pointer;">
<img src="{{ imgArray[1] }}" />
總得分</div>
</th>
<th>
<div (click)="toOrder(2)" style="cursor: pointer;">
<img src="{{ imgArray[2] }}" />
出賽場次</div>
</th>
<th>
<div (click)="toOrder(3)" style="cursor: pointer;">
<img src="{{ imgArray[3] }}" />
平均得分</div>
</th>
<th>
<div (click)="toOrder(4)" style="cursor: pointer;">
<img src="{{ imgArray[4] }}" />
賽季</div>
</th>
<th>
<div (click)="toOrder(5)" style="cursor: pointer;">
<img src="{{ imgArray[5] }}" />
出生日期</div>
</th>
<th>
<div (click)="toOrder(6)" style="cursor: pointer;">
<img src="{{ imgArray[6] }}" />
背號</div>
</th>
<th>
<div (click)="toOrder(7)" style="cursor: pointer;">
<img src="{{ imgArray[7] }}" />
身高</div>
</th>
</tr>
</thead>
接下來是ts檔程式碼的部份:
/**
* 進行排序前,判斷箭頭方向
*/
toOrder(value: number) {
for (let i in this.mode) {
Number(i) != value ? this.mode[i] = this.tableOrderMode.Halt : null;
this.imgArray[i] = this.state(this.mode[i]);
}
if (this.mode[value] != this.tableOrderMode.Halt) {
this.mode[value] == this.tableOrderMode.Up ? this.mode[value] = this.tableOrderMode.Down :
this.mode[value] = this.tableOrderMode.Up;
} else {
this.mode[value] = this.tableOrderMode.Down;
}
this.orderArray(value, this.mode[value]);
this.imgArray[value] = this.state(this.mode[value]);
}
<div (click)="toOrder(2)" style="cursor: pointer;">
在標題欄位中,加入click的觸發事件,並傳入欄位編號,
從「姓名」欄位由0開始,往下一個欄位依序給予編號,
最後一個「身高」欄位標題,排序為數字7。
在此之前,宣告並預設一個mode的陣列,專門存放所有欄位標題目前顯示的箭頭狀態,
狀態分別有halt (未點選)、up (點選資料往上排序)與down (點選資料往下排序)三種。
在看事件觸發的方法演算法前,先看到mode這個變數的預設值:
this.mode = [this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt
, this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt
, this.tableOrderMode.Halt, this.tableOrderMode.Halt];
所有欄位標題顯示狀態,都是未點選時的halt狀態。
當點選某欄位的標題之後,觸發事件,接著進入事件函式,
讓我們看到下面這段,函式中的判斷式:
if (this.mode[value] != this.tableOrderMode.Halt) {
this.mode[value] == this.tableOrderMode.Up ? this.mode[value] = this.tableOrderMode.Down :
this.mode[value] = this.tableOrderMode.Up;
} else {
this.mode[value] = this.tableOrderMode.Down;
}
這段的判斷大致上為:
1. 判斷點選當下,該欄位的狀態為何。
2. 如果狀態為halt,則進入down的狀態,預設以高排到低。
3. 如果狀態不為halt,則當up狀態時,給予down的狀態,反之亦然。如此可以馬上進行反向排序,使用者將對資料內容一目瞭然。
接著我們再看進入該事件函式時的程式碼:
for (let i in this.mode) {
Number(i) != value ? this.mode[i] = this.tableOrderMode.Halt : null;
this.imgArray[i] = this.state(this.mode[i]);
}
此部份在於,如果不是滑鼠click事件觸發的欄位,則全部回歸halt狀態。
同時,在此呼叫另一個叫state的函式:
/**
* 依據箭頭方向呈現對應的箭頭圖片
*/
state(value: number): string {
let url = '';
switch (value) {
case this.tableOrderMode.Up:
url = './assets/images/arrow_up.png';
break;
case this.tableOrderMode.Down:
url = './assets/images/arrow_down.png';
break;
case this.tableOrderMode.Halt:
url = './assets/images/arrow_halt.png';
break;
}
return url;
}
此部份在於根據不同狀態,載入相對應的圖片。
html的img圖片部份:
<img src="{{ imgArray[0] }}" />
imgArray為存放圖片位址的陣列,根據其不同的編號,給予相對應的值。
存在的角色與mode陣列很像,所以不免俗的,在頁面載入時,必須給予預設值:
this.imgArray = ['./assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png'
, './assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png'];
與mode存放狀態的陣列一樣,imgArray也是先預設為載入halt的圖片。
並且在此click事件觸發的函式中,將非點選到的欄位標題,全部回到halt的圖片呈現。
而在此事件函式後面,還有一段程式如下:
this.orderArray(value, this.mode[value]);
this.imgArray[value] = this.state(this.mode[value]);
除了判斷該點選的欄位,須呈現相對應的狀態圖片之外,還呼叫了另一個函式orderArray:
/**
* 排序列表
*/
orderArray(value: number, direction: TableOrderMode) {
const items = ['name', 'total', 'game', 'average', 'season', 'birth',
'jersey', 'height'];
const compareItem = items[value];
if (compareItem === 'birth') {
this.list.rows = this.list.rows.sort(function (a, b) {
return new Date(a[compareItem]) < new Date(b[compareItem]) ? -1 : 1;
});
} else if (compareItem === 'name') {
this.list.rows = this.list.rows.sort(function (a, b) {
return b[compareItem].localeCompare(a[compareItem], "zh-TW");
});
} else {
this.list.rows = this.list.rows.sort(function (a, b) {
return Number(a[compareItem]) < Number(b[compareItem]) ? -1 : 1;
});
}
direction == this.tableOrderMode.Up ? null :
this.list.rows = this.list.rows.reverse();
}
此部份為進行列表排序的判斷函式,呼叫時需要帶入兩個參數,
分別為該欄位的編號value,以及目前點選時呈現的狀態direction。
在這裡同樣給它一個陣列,代表各欄位的名稱,方便接下來的排序判斷:
const items = ['name', 'total', 'game', 'average', 'season', 'birth',
'jersey', 'height'];
接下來進入排序的判斷。
如果點選的欄位是出生日期的話,以new出日期進行日期先後的比較排序:
if (compareItem === 'birth') {
this.list.rows = this.list.rows.sort(function (a, b) {
return new Date(a[compareItem]) < new Date(b[compareItem]) ? -1 : 1;
});
如果點選的欄位是球員姓名的話,以字首的字元比較進行排序:
else if (compareItem === 'name') {
this.list.rows = this.list.rows.sort(function (a, b) {
return b[compareItem].localeCompare(a[compareItem], "zh-TW");
});
}
剩下的欄位,像是總得分 (total)、出賽場次 (game)、平均得分 (average)、生涯賽季 (season)、球衣背號 (jersey)以及身高 (height),
皆轉成number作數字大小的比較排序:
else {
this.list.rows = this.list.rows.sort(function (a, b) {
return Number(a[compareItem]) < Number(b[compareItem]) ? -1 : 1;
});
}
接下來以欄位目前的狀態,決定列表是否要反過來排序:
direction == this.tableOrderMode.Up ? null :
this.list.rows = this.list.rows.reverse();
如此一來,以Angular既有技術完成表格欄位分類排序的任務,就大功告成!
在此補上表格各欄位body的部份:
<tbody>
<tr class="text-center" *ngFor="let item of list.rows">
<td data-th="頭像">
<img class="photo" src="./assets/images/{{ item.photo }}" />
</td>
<td data-th="姓名">
<span class="td-col">{{item.name}}</span>
</td>
<td data-th="總得分">
<span class="td-col">{{item.total | number}} 分</span>
</td>
<td data-th="出賽場次">
<span class="td-col">{{item.game | number}} 場</span>
</td>
<td data-th="平均得分">
<span class="td-col">{{item.average}} 分</span>
</td>
<td data-th="賽季">
<span class="td-col">{{item.season}} 季</span>
</td>
<td data-th="出生日期">
<span class="td-col">{{item.birth | date:'yyyy-MM-dd'}}</span>
</td>
<td ata-th="背號">
<span class="td-col">{{item.jersey}} 號</span>
</td>
<td data-th="身高">
<span class="td-col">{{item.height}} cm</span>
</td>
</tr>
</tbody>
接下來,是時候回頭比較PrimeNG的程式碼部份了!
PrimeNG套件程式碼:
HTML
<div class="card">
<h5>PrimeNG Table Sort</h5>
<p-table [value]="scoreList">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="photo">頭像 <p-sortIcon field="code"></p-sortIcon></th>
<th pSortableColumn="name">姓名 <p-sortIcon field="name"></p-sortIcon></th>
<th pSortableColumn="total">總得分 <p-sortIcon field="total"></p-sortIcon></th>
<th pSortableColumn="game">出賽場數 <p-sortIcon field="game"></p-sortIcon></th>
<th pSortableColumn="average">平均得分 <p-sortIcon field="average"></p-sortIcon></th>
<th pSortableColumn="season">球季 <p-sortIcon field="season"></p-sortIcon></th>
<th pSortableColumn="birth">出生 <p-sortIcon field="birth"></p-sortIcon></th>
<th pSortableColumn="jersey">背號 <p-sortIcon field="jersey"></p-sortIcon></th>
<th pSortableColumn="height">身高 <p-sortIcon field="height"></p-sortIcon></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-player>
<tr>
<td><img src="./assets/images/{{player.photo}}" /></td>
<td>{{player.name}}</td>
<td>{{player.total | number}} 分</td>
<td>{{player.game | number}} 場</td>
<td>{{player.average}} 分</td>
<td>{{player.season}} 季</td>
<td>{{player.birth | date:'yyyy-MM-dd'}}</td>
<td>{{player.jersey}}</td>
<td>{{player.height}} cm</td>
</tr>
</ng-template>
</p-table>
</div>
component.ts的部份可以先省略,只有把資料塞進程式裡而已。
資料排序的部份,PrimeNG套件直接在HTML就以他們提供的CDK做掉。
當然,module的部份必須安裝Animations與CDK,以及引入相關的模組,才可以順利使用該功能。
安裝PrimeNG Animations的CLI指令如下:
npm install @angular/animations --save
引入Modules:
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
//...
],
安裝PrimeNG CDK:
npm install @angular/cdk --save
引入TableModule:
import { TableModule } from 'primeng/table';
@NgModule({
imports: [
TableModule,
//...
],
接下來,我們看看PrimeNG的呈現畫面。
首先,以總得分由高至低進行排序:
接下來是以身高由低到高的排序:
PrimeNG套件提供的icon,是不是還算精緻呢?
當然,css都可以再自行客製化調整,目前使用的demo是最陽春的版本。
最後,筆者再將兩個專案的所有HTML與compoent.ts檔案的程式碼,po出來對照比較一次。
比較喜歡哪種方法,就請各位看倌們,自行斟酌參考了。
HTML (非套件)
<div class="container-block">
<div class="container-fluid">
<h5 class="mt-4">Angular非套件表格排序</h5>
<div class="form-info">
<div class="info-container">
<table class="table-rwd table table-striped table-hover">
<colgroup>
<col style="width: 120px;">
<col style="width: 120px;">
<col style="width: 100px;">
<col style="width: 100px;">
<col style="width: 90px;">
<col style="width: 90px;">
<col style="width: 90px;">
<col style="width: 90px;">
<col style="width: 100px;">
</colgroup>
<thead>
<tr class="text-center">
<th>
頭像
</th>
<th>
<div (click)="toOrder(0)" style="cursor: pointer;">
<img src="{{ imgArray[0] }}" />
姓名</div>
</th>
<th>
<div (click)="toOrder(1)" style="cursor: pointer;">
<img src="{{ imgArray[1] }}" />
總得分</div>
</th>
<th>
<div (click)="toOrder(2)" style="cursor: pointer;">
<img src="{{ imgArray[2] }}" />
出賽場次</div>
</th>
<th>
<div (click)="toOrder(3)" style="cursor: pointer;">
<img src="{{ imgArray[3] }}" />
平均得分</div>
</th>
<th>
<div (click)="toOrder(4)" style="cursor: pointer;">
<img src="{{ imgArray[4] }}" />
賽季</div>
</th>
<th>
<div (click)="toOrder(5)" style="cursor: pointer;">
<img src="{{ imgArray[5] }}" />
出生日期</div>
</th>
<th>
<div (click)="toOrder(6)" style="cursor: pointer;">
<img src="{{ imgArray[6] }}" />
背號</div>
</th>
<th>
<div (click)="toOrder(7)" style="cursor: pointer;">
<img src="{{ imgArray[7] }}" />
身高</div>
</th>
</tr>
</thead>
<tbody>
<tr class="text-center" *ngFor="let item of list.rows">
<td data-th="頭像">
<img class="photo" src="./assets/images/{{ item.photo }}" />
</td>
<td data-th="姓名">
<span class="td-col">{{item.name}}</span>
</td>
<td data-th="總得分">
<span class="td-col">{{item.total | number}} 分</span>
</td>
<td data-th="出賽場次">
<span class="td-col">{{item.game | number}} 場</span>
</td>
<td data-th="平均得分">
<span class="td-col">{{item.average}} 分</span>
</td>
<td data-th="賽季">
<span class="td-col">{{item.season}} 季</span>
</td>
<td data-th="出生日期">
<span class="td-col">{{item.birth | date:'yyyy-MM-dd'}}</span>
</td>
<td ata-th="背號">
<span class="td-col">{{item.jersey}} 號</span>
</td>
<td data-th="身高">
<span class="td-col">{{item.height}} cm</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
HTML (PrimeNG)
<div class="card">
<h5>PrimeNG Table Sort</h5>
<p-table [value]="scoreList">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="photo">頭像 <p-sortIcon field="code"></p-sortIcon></th>
<th pSortableColumn="name">姓名 <p-sortIcon field="name"></p-sortIcon></th>
<th pSortableColumn="total">總得分 <p-sortIcon field="total"></p-sortIcon></th>
<th pSortableColumn="game">出賽場數 <p-sortIcon field="game"></p-sortIcon></th>
<th pSortableColumn="average">平均得分 <p-sortIcon field="average"></p-sortIcon></th>
<th pSortableColumn="season">球季 <p-sortIcon field="season"></p-sortIcon></th>
<th pSortableColumn="birth">出生 <p-sortIcon field="birth"></p-sortIcon></th>
<th pSortableColumn="jersey">背號 <p-sortIcon field="jersey"></p-sortIcon></th>
<th pSortableColumn="height">身高 <p-sortIcon field="height"></p-sortIcon></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-player>
<tr>
<td><img src="./assets/images/{{player.photo}}" /></td>
<td>{{player.name}}</td>
<td>{{player.total | number}} 分</td>
<td>{{player.game | number}} 場</td>
<td>{{player.average}} 分</td>
<td>{{player.season}} 季</td>
<td>{{player.birth | date:'yyyy-MM-dd'}}</td>
<td>{{player.jersey}}</td>
<td>{{player.height}} cm</td>
</tr>
</ng-template>
</p-table>
</div>
HTML小結
HTML的部份,由於專案中有使用特別的class、加入click事件以及icon圖示,因此表格的程式碼稍微厚了一點,
不過整體來說,與PrimeNG的特殊p開頭標籤,程式碼的厚度差異相去不遠。
但以整潔性來說,PrimeNG提供的套件可讀性較高,主要差在不需自訂加入click事件以及箭頭圖示。
順便一提,Angular既有的pipe功能,在呈現數字的三位一撇,以及日期欄位的呈現格式上,就已經省下許多開發時間了。
PrimeNG又再增加開發上的便利性。
component.ts (非套件)
import { Component, OnInit } from '@angular/core';
import { ScoreList } from '../../../domain/company';
import { TableOrderMode } from '../../../domain/table-order.enum';
@Component({
selector: 'app-customer-export',
templateUrl: './customer-export.component.html',
styleUrls: ['./customer-export.component.css']
})
export class CustomerExportComponent implements OnInit {
list: ListPage<ScoreList>;
tableOrderMode: typeof TableOrderMode = TableOrderMode;
mode: number[];
imgArray: string[];
constructor(
private _service: CustomerExportService,
) {
this.list = { pageTotal: 0, rowsCount: 0, rows: [], currentPage: 1 };
this.mode = [this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt
, this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt, this.tableOrderMode.Halt
, this.tableOrderMode.Halt, this.tableOrderMode.Halt];
this.imgArray = ['./assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png'
, './assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png', './assets/images/arrow_halt.png'];
}
ngOnInit() {
this.list.rows = [
{
name: 'Kareem Abdul-Jabbar',
total: 38387,
game: 1560,
average: 24.6,
birth: new Date('1947-04-16'),
season: 20,
jersey: 33,
height: 218,
photo: 'Jabbar.jpg'
},
{
name: 'Karl Malone',
total: 36928,
game: 1476,
average: 25.0,
birth: new Date('1963-07-24'),
season: 19,
jersey: 32,
height: 206,
photo: 'MailMan.jpg'
},
{
name: 'LeBron James',
total: 34241,
game: 1265,
average: 27.5,
birth: new Date('1984-12-30'),
season: 17,
jersey: 23,
height: 206,
photo: 'LeBron.jpg'
},
{
name: 'Kobe Bryant',
total: 33643,
game: 1346,
average: 25.0,
birth: new Date('1978-08-23'),
season: 20,
jersey: 24,
height: 198,
photo: 'Kobe.jpeg'
},
{
name: 'Michael Jordan',
total: 32292,
game: 1072,
average: 30.1,
birth: new Date('1963-02-17'),
season: 14,
jersey: 23,
height: 198,
photo: 'Jordan.jpg'
},
{
name: 'Dirk Nowitzki',
total: 31560,
game: 1522,
average: 20.7,
birth: new Date('1978-06-19'),
season: 21,
jersey: 41,
height: 213,
photo: 'Dirk.jpg'
},
{
name: 'Wilt Chamberlain',
total: 31419,
game: 1045,
average: 30.1,
birth: new Date('1936-08-21'),
season: 15,
jersey: 13,
height: 216,
photo: 'Wilt.jpg'
},
{
name: "Shaquille O'Neal",
total: 28596,
game: 1207,
average: 23.7,
birth: new Date('1972-03-06'),
season: 19,
jersey: 34,
height: 216,
photo: 'Shaq.jpg'
},
{
name: "Moses Malone",
total: 27409,
game: 1329,
average: 20.6,
birth: new Date('1955-03-23'),
season: 21,
jersey: 22,
height: 208,
photo: 'Moses.jpeg'
},
{
name: "Elvin Hayes",
total: 27313,
game: 1303,
average: 21.0,
birth: new Date('1945-11-17'),
season: 16,
jersey: 44,
height: 206,
photo: 'Hayes.jpg'
},
]
}
/**
* 進行排序前,判斷箭頭方向
*/
toOrder(value: number) {
for (let i in this.mode) {
Number(i) != value ? this.mode[i] = this.tableOrderMode.Halt : null;
this.imgArray[i] = this.state(this.mode[i]);
}
if (this.mode[value] != this.tableOrderMode.Halt) {
this.mode[value] == this.tableOrderMode.Up ? this.mode[value] = this.tableOrderMode.Down :
this.mode[value] = this.tableOrderMode.Up;
} else {
this.mode[value] = this.tableOrderMode.Down;
}
this.orderArray(value, this.mode[value]);
this.imgArray[value] = this.state(this.mode[value]);
}
/**
* 排序列表
*/
orderArray(value: number, direction: TableOrderMode) {
const items = ['name', 'total', 'game', 'average', 'season', 'birth',
'jersey', 'height'];
const compareItem = items[value];
if (compareItem === 'birth') {
this.list.rows = this.list.rows.sort(function (a, b) {
return new Date(a[compareItem]) < new Date(b[compareItem]) ? -1 : 1;
});
} else if (compareItem === 'name') {
this.list.rows = this.list.rows.sort(function (a, b) {
return b[compareItem].localeCompare(a[compareItem], "zh-TW");
});
} else {
this.list.rows = this.list.rows.sort(function (a, b) {
return Number(a[compareItem]) < Number(b[compareItem]) ? -1 : 1;
});
}
direction == this.tableOrderMode.Up ? null :
this.list.rows = this.list.rows.reverse();
}
/**
* 依據箭頭方向呈現對應的箭頭圖片
*/
state(value: number): string {
let url = '';
switch (value) {
case this.tableOrderMode.Up:
url = './assets/images/arrow_up.png';
break;
case this.tableOrderMode.Down:
url = './assets/images/arrow_down.png';
break;
case this.tableOrderMode.Halt:
url = './assets/images/arrow_halt.png';
break;
}
return url;
}
}
Component.ts (PrimeNG)
import { Component } from '@angular/core';
import { ScoreList } from './score-list';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'png-table';
scoreList: ScoreList[];
constructor() {
this.scoreList = [
{
name: 'Kareem Abdul-Jabbar',
total: 38387,
game: 1560,
average: 24.6,
birth: new Date('1947-04-16'),
season: 20,
jersey: 33,
height: 218,
photo: 'Jabbar.jpg'
},
{
name: 'Karl Malone',
total: 36928,
game: 1476,
average: 25.0,
birth: new Date('1963-07-24'),
season: 19,
jersey: 32,
height: 206,
photo: 'MailMan.jpg'
},
{
name: 'LeBron James',
total: 34241,
game: 1265,
average: 27.5,
birth: new Date('1984-12-30'),
season: 17,
jersey: 23,
height: 206,
photo: 'LeBron.jpg'
},
{
name: 'Kobe Bryant',
total: 33643,
game: 1346,
average: 25.0,
birth: new Date('1978-08-23'),
season: 20,
jersey: 24,
height: 198,
photo: 'Kobe.jpeg'
},
{
name: 'Michael Jordan',
total: 32292,
game: 1072,
average: 30.1,
birth: new Date('1963-02-17'),
season: 14,
jersey: 23,
height: 198,
photo: 'Jordan.jpg'
},
{
name: 'Dirk Nowitzki',
total: 31560,
game: 1522,
average: 20.7,
birth: new Date('1978-06-19'),
season: 21,
jersey: 41,
height: 213,
photo: 'Dirk.jpg'
},
{
name: 'Wilt Chamberlain',
total: 31419,
game: 1045,
average: 30.1,
birth: new Date('1936-08-21'),
season: 15,
jersey: 13,
height: 216,
photo: 'Wilt.jpg'
},
{
name: "Shaquille O'Neal",
total: 28596,
game: 1207,
average: 23.7,
birth: new Date('1972-03-06'),
season: 19,
jersey: 34,
height: 216,
photo: 'Shaq.jpg'
},
{
name: "Moses Malone",
total: 27409,
game: 1329,
average: 20.6,
birth: new Date('1955-03-23'),
season: 21,
jersey: 22,
height: 208,
photo: 'Moses.jpeg'
},
{
name: "Elvin Hayes",
total: 27313,
game: 1303,
average: 21.0,
birth: new Date('1945-11-17'),
season: 16,
jersey: 44,
height: 206,
photo: 'Hayes.jpg'
},
]
}
}
Component.ts小結
component的部份,差異就更多了一點。
以長度來看,總體而言,非套件的程式碼,多了兩個陣列的預設值,以及三個click事件觸發的後續函式。
雖然多幾個函式看來似乎沒多多少,但在撰寫程式的過程中,所消耗的時間與腦力,也是相對可觀的。
而PrimeNG套件的部份,若將資料另外獨立出json檔載入的話,該檔案幾乎加不到多少程式碼。
在整潔度與維護方便性上,差距更是顯著。
總結
整體而言,相較於自己寫出一樣的功能,套件還是比較方便的。
不過在此還是要感謝Angular框架的發明,已經將傳統JavaScript所必須刻出的程式碼,作大幅度的縮減了!
不管白貓黑貓,會抓老鼠的就是好貓!不管過程如何順利或曲折,可以解決需求最重要!
如果可以多方汲取各路江湖套件,並且導入專案中善用的話,確實可以省下不少開發效率,不過前題是,您必須先花工作之餘的時間多做study或survey;
然而,若是遇到無法如期使用套件的時候,像是本專題遇到的版本過低不相容的問題時,平時加強前端框架Angular、TypeScript等,乃至於JavaScript與ES6的基本功,相較起來似乎是個更重要的事。如果基本功夠熟稔的話,一樣可以解決各式各樣的需求。
當然,不管有沒有使用套件,都是要花工作之餘的時間多study或survey,才是個正港的pro級前端玩家啦!
與大家共勉之。