0%

Angular 筆記 - 如何透過 groupby 建立檔案與應用

image

本次情境

本次需求是要將同一個客戶的資料重新組合在一起,並將有相同的資料要跨欄在 table 呈現,不要問我為什麼後端不處理…就記錄一下這次專案上遇到的問題…

資料範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
data=[
{
BRANCH: "台北營業所",
CARMDL: "car",
CFID: "11111111",
INTIME: "2016-08-08 10:11:22",
ORDERFLAG: "1",
SALESNM: "業務A",
SORTFLAG: "1",
USERID: "",
USERNM: "客戶A",
},
{
BRANCH: "桃園營業所",
CARMDL: "truck",
CFID: "11111111",
INTIME: "2016-08-08 10:09:55",
ORDERFLAG: "1",
SALESNM: "業務B",
SORTFLAG: "2",
USERID: "",
USERNM: "客戶A",
},
{
BRANCH: "新莊營業所",
CARMDL: "train",
CFID: "22222222",
INTIME: "2016-06-08 15:28:55",
ORDERFLAG: "2",
SALESNM: "業務C",
SORTFLAG: "1",
USERID: "",
USERNM: "客戶B",
},
{
BRANCH: "台北營業所",
CARMDL: "bike",
CFID: "22222222",
INTIME: "2016-07-07 09:59:00",
ORDERFLAG: "2",
SALESNM: "業務D",
SORTFLAG: "2",
USERID: "",
USERNM: "客戶B",
},
]

觀察相同點

以前兩筆資料為例,可以看到有 CFIDORDERFLAGUSERNM 這三個欄位相同,CFID 看起來就是這筆資料的唯一代碼,所以我想說要從這裡下手。

groupby

首先在 util.service 建立一個 groupby 方法。

service 可自訂義,只是剛好專案把很多功能類的方法寫在此 service 內

util.service.ts

1
2
3
4
5
6
7
8
groupBy<T>(array: Array<T>, key: any): any {
return array.reduce((group, item) => {
const val = item[key];
group[val] = group[val] || [];
group[val].push(item);
return group;
}, {});
}

使用 groupby 轉資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
getData() {
this._util.loadingShow();
let req: B006Q01Request = {
DLRCD: this.user.DLRCD,
BRNHCD: this.user.BRNHCD,
SECTCD: this.user.SECTCD,
};

this._serviceB06
.getMVE2B006Q01(req)
.pipe(
map((res) => res.ResultData.MVE2MVE2B006_Q01),
finalize(() => {
this._util.loadingHide();
})
)
.subscribe((res) => {
this.mveB06Q1Data = res;
this.groupData = this._util.groupBy(this.mveB06Q1Data, "CFID"); //*這段是重點
this.mveB06Q1Data = this.groupData; //*替換原本的陣列
});
}

使用 groupby 方法重組資料,第一個參數要帶目前的資料陣列,第二個參數要帶以什麼為基準的 key。
console 出來會看到資料變成這樣

1
2
11111111:(2)[(...),(...)],
22222222:(2)[(...),(...)]

然後到這邊我就卡住了,因為沒接過這樣的資料,後來查詢一些資料後才知道,前面是我得到的 key 值,透過 groupby 的方法已經幫我把同類型的資料內容分類好了,所以我已經得到了資料。

重點是怎麼渲染在畫面上

網路上找了許多方法,大部分都是告訴我怎麼用 groupby 重組資料,但比較少渲染在資料上,有找到的也無法完全符合我目前的情境。

後來觀察表格發現我要把目前同一個 key 的資料,有共同的內容要用 rowspan 的方式渲染在畫面上,不同的要維持原本的 table 呈現(如一開始畫面所示)。

跑雙迴圈

Object.keys()

  • 得到重組資料後,一開始會得到一個物件裡面又包著我重組的陣列資料,想說去查一下物件怎麼轉成陣列的方法,後來去查,找不到,再換一個想法,就是如何取到物件中陣列的值,後來發現有一個 ES5 的語法是 Object.keys() 的方法,有成功取到值,但後來又卡住不知道怎麼渲染在畫面上,再去找資料。

keyValue

  • 因為我知道要把資料渲染在畫面上,可以用 ngfor 來渲染資料,後來找到這篇,我把原本接 API 的內容替換成重組資料。並使用 Angular 的 pipe 方法,keyvalue,讓其跑迴圈時會遍歷資料所有的 key 跟 value。

後來才發現 groupby 組回來的資料外面是一個物件,因為物件沒有 forEach 可以用,但因透過 keyValue 的方式可以去找出物件的 keyvalue

key 就是我的 CFIDvalue 就是陣列。

來渲染網頁吧

僅記錄 tbody 的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<tbody>
<ng-container *ngFor="let row of mveB06Q1Data | keyvalue; let i = index">
<tr
class="Nb-bg"
[id]="item.CFID"
*ngFor="let item of row.value; let j = index"
>
<ng-container *ngIf="j == 0">
<td [attr.rowspan]="row.value.length" class="none">
<label>
<input
type="checkbox"
class="checkbox"
[checked]="isCheck(item.CFID)"
(click)="clickChecked(item.CFID, $event)"
/>
<span class="check-icon"></span>
</label>
</td>
<td [attr.rowspan]="row.value.length" class="NB">{{ i + 1 }}</td>
<td class="name" [attr.rowspan]="row.value.length">
{{ item.USERNM }}
</td>
<td class="name" [attr.rowspan]="row.value.length" data-title="統編:">
{{ item.USERID }}
</td>
</ng-container>
<td data-title="車&emsp;&emsp;型:">{{ item.CARMDL }}</td>
<td data-title="營業所 :">{{ item.BRANCH }}</td>
<td data-title="業&emsp;&emsp;代:">{{ item.SALESNM }}</td>
<td data-title="輸入時間:">{{ item.INTIME }}</td>
</tr>
</ng-container>
</tbody>
  • 我把要共同顯示的部分用一個 ng-container 包起來,然後讓它跑第一層迴圈,並且後面使用 keyvalue 這個 pipe 方法,讓我可以取到 keyvalue。這邊的 key 不用特別寫的原因是因為我在 groupby 的時候已經取出來了,所以使用 keyvalue 時,會自己去抓到 key。
  • tr 上跑第二個迴圈,因為 value 才是陣列資料本身的欄位,所以第二層迴圈是 row.value,這邊是小細節,必須注意,後來我也有用 json pipe 的去檢查我取到的內容,才發現有 value 這個欄位,在 console 時不會看到。
  • 在共同要顯示成一個欄位的 td,我外層再使用一個 ng-container 包起來,是要下判斷只取第二層迴圈的第一筆資料,原因是內容都一樣,只要選一個出來顯示即可,第二層迴圈才是真正的陣列資料。
  • 在要上下合併的欄位使用 [attr.rowspan]="row.value.length" 去自訂義 rowspan 的數值,期數值為該陣列資料的長度。

其他就讓它自己跑迴圈渲染,這樣就得到我要的畫面呈現了。

參考資料