Angular PrimeNG Table Sort 表格排序

Angular PrimeNG CDK與Angular非套件資料分類排序功能實作差異

江秋霖 2020/11/27 17:20:43
2807

前言

每一個前端工程師或許都有在專案遇到過這個問題,客戶提出的需求,在新的套件技術中,已經可以輕鬆達成。
像是在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級前端玩家啦!

與大家共勉之。

江秋霖