[Angular] PrimeNG開發兩三事

前陣子公司的一個產品算是告一段落,這次使用PrimeNG作為元件的基底,但是在使用的過程中,也是有遇到一些值得記錄的事項,因此特別寫了這篇。

Message

物件的格式是這樣{severity:string, summary:string, details:string},但每次都要重複寫那些屬性有點多餘,因此我將這個封裝成service,直接提供successerrorinfowarn四個方法。

然後把message的物件用subject傳送,這樣就能在app.component.html直接用async的方式接起來。

另外IE11要特別注意一下,details如果沒給值,畫面上會出現null的字,然後就會被殺了orz
這個issue確定會在5.2.4改掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Injectable } from '@angular/core';
import { Message } from 'primeng/primeng';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class MessageService {
messages$ = new Subject<Message[]>();
constructor() {}

success(content: string) {
this.messages$.next([{ severity: 'success', summary: content, detail: '' }]);
}
error(content: string) {
this.messages$.next([{ severity: 'error', summary: content, detail: '' }]);
}
info(content: string) {
this.messages$.next([{ severity: 'info', summary: content, detail: '' }]);
}
warn(content: string) {
this.messages$.next([{ severity: 'warn', summary: content, detail: '' }]);
}
}

1
<p-growl [value]="msgService.messages$ | async"></p-growl>

Menu/Bread

這邊提供一個小tip,如果說route有很相似的,但在menu/bread上面不想要被同時選取,這時候可以加上routerLinkActiveOptions: { exact: true },這個在angular官網有被提到過。

route的情境像是這樣,沒有加上exact的情況,進入產品頁面的時候,是三個都會被反白,但這基本上不是我們要的效果,因此就要加上,讓他要完整路徑都對才會反白。

1
2
3
4
5
menuList: MenuItem[] = [
{label: '產品', routerLink: ['/product'], routerLinkActiveOptions: { exact: true }},
{label: '特賣', routerLink: ['/product/special'], routerLinkActiveOptions: { exact: true }},
{label: '團購', routerLink: ['/product/together'], routerLinkActiveOptions: { exact: true }}
];

Dialog

包在Component中

如果你也有把dialog整個包進去component的需求,那這個方法就很適合你!

dialog的開啟是透過boolean來控制,當寫成component的時候,參數並不會被改變,這時候dialog就不會再次被開啟,因此我們的好朋友subject出現啦!

首先child component可以這樣寫

1
2
3
<p-dialog [(visible)]="displayFlag" [modal]="true">
show something
</p-dialog>
1
2
3
4
5
6
7
8
9
10
export class YourComponent implements OnInit{
@Input() data: Subject<boolean>;
displayFlag: boolean;

ngOnInit() {
this.data.subscribe(value => {
this.displayFlag = true;
});
}
}

再來parent component的地方,就直接把宣告好的subject放進去,然後每次要開啟就呼叫subject

1
2
<button (click)="open()">open dialog</button>
<app-your [data]="openDialog$"></app-your>
1
2
3
4
5
6
7
export class ParentComponent {
openDialog$ = new Subject<boolean>();
open(){
//這邊傳true或false都不影響開啟,重點是要讓subject有值過去觸發
this.openDialog$.next(true);
}
}

onHide

我們有一個需求是表單填寫的內容放在Dialog中,如果有填寫但最後不儲存,離開前需要提示,像是下面這個範例

1
2
3
4
5
6
7
8
9
<p-dialog [(visible)]="displayDialog" [modal]="true">
<!-- 表單內容 -->
<form [formGroup]="form">
</form>
<p-footer>
<button pButton type="button" class="ui-button-secondary" label="離開" (click)="close()"></button>
<button pButton type="button" class="ui-button-primary" label="儲存" (click)="save()"></button>
</p-footer>
</p-dialog>
1
2
3
4
5
6
7
8
9
10
@Component()
export class MyComponent{
displayDialog: boolean;
form: FormGroup;

close(){
//show confirm
this.displayDialog = false;
}
}

但是右上角有個X,按的時候也視同關閉,因此我們需要加上一個事件onHide來做控制,但是當我們像上面範例,把displayDialog改成false,這時候其實也會觸發onHide的事件,因此就要稍微注意一下順序的控制,才能避免不斷的觸發,大致的作法像是這樣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component()
export class MyComponent implements OnInit{
displayDialog: boolean;
form: FormGroup;
valueChange: boolean;

ngOnInit(){
this.form.valueChanges
.subscribe(p => this.valueChange = true);
}

close(){
this.displayDialog = false;
}

hide(){
if(this.valueChange){
// show confirm
} else {
this.displayDialog = false;
}
}
}

若將file之類的元件放在dialog中,開啟的時候會觸發一個dection change的錯誤,但這個其實不會真的影響功能操作,只是會讓你看起來不開心QQ,當初遇到這個問題時還有去開issue只是官方沒人理我TAT

FileUpload

在使用這個元件的時候,因為畫面的關係,所以採用mode="basic",但是這會引發一些狀況,像是maxFileSize上傳的檔案太大應該要提示訊息,但這種模式下不會…TAT

另外,每次上傳完都要將資料清除,不然再次按上傳的時候,會沒辦法選取直接傳前一次檔案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component()
export class MyComponent implements OnInit{
@ViewChild(FileUpload) fileUpload: FileUpload;

ngOnInit(){
}

uploadFile(event) {
this.fileService.uploadFile(this.id, this.type, event.files)
.subscribe(fileIds => {
this.fileUpload.clear();
});
}
}

DataTable

這邊我們用的是DataTable而不是新的TurboTable,就只是因為當時只有一個可以用沒得選XD,這個元件其實蠻多小地方要注意的,很容易萬劫不復阿QQ

官方已經不再維護DataTable這個元件了…請大家轉用TurboTable

Sort

在官方範例中,sort很簡單,就是在column中,加上sortable這個屬性,但如果你有搭配headerColumnGroup,那就要注意一下放的位置,除了在下面的column要放field以外,也要在header中的column加上field,這樣元件才會知道要排序data中的哪個欄位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<p-dataTable [value]="sales">
<p-headerColumnGroup>
<p-row>
<p-column header="Brand" rowspan="2"></p-column>
<p-column header="Sale Rate" colspan="4"></p-column>
</p-row>
<p-row>
<!-- 正確寫法 -->
<p-column header="Last Year" [sortable]="true" field="lastYearSale"></p-column>
<!-- 錯誤寫法 -->
<p-column header="This Year" [sortable]="true"></p-column>
<p-column header="Last Year"></p-column>
<p-column header="This Year"></p-column>
</p-row>
</p-headerColumnGroup>

<p-column field="brand"></p-column>
<p-column field="lastYearSale"></p-column>
<p-column field="thisYearSale"></p-column>
<p-column field="lastYearProfit"></p-column>
<p-column field="thisYearProfit"></p-column>
</p-dataTable>

另外一個要特別注意的點,就是資料的型別要統一,不然排序也會亂掉

這邊提供一個我做出來的範例,可以發現點year那個欄位,VM這筆資料竟然獨立在那邊,其實原因很簡單,資料中有nullundefinenumber三種形態,所以對元件來說會把VM那筆獨立排

1
2
3
4
5
6
7
8
9
10
11
12
[
{"brand": "VW", year: null, "color": "Orange", "vin": "dsad231ff"},
{"brand": "Audi", "color": "Black", "vin": "gwregre345"},
{"brand": "Renault", "year": 2005, "color": "Gray", "vin": "h354htr"},
{"brand": "BMW", "year": 2003, "color": "Blue", "vin": "j6w54qgh"},
{"brand": "Mercedes", "year": 1995, "color": "Orange", "vin": "hrtwy34"},
{"brand": "Volvo", "color": "Black", "vin": "jejtyj"},
{"brand": "Honda", "year": 2012, "color": "Yellow", "vin": "g43gr"},
{"brand": "Jaguar", "color": "Orange", "vin": "greg34"},
{"brand": "Ford", "year": 2000, "color": "Black", "vin": "h54hw5"},
{"brand": "Fiat", "year": 2013, "color": "Red", "vin": "245t2s"}
]

Edit

行內編輯

官方給的範例中,edit的功能只能一個欄位一個欄位的編輯,但如果想要的是整個row一起開放編輯,並且有儲存那些的功能怎麼辦?

這樣的功能我是自己刻,用一個屬性來控制現在是編輯還是檢視

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<p-dataTable [value]="sales" [rowStyleClass]="checkHighlight">
<p-column>
<ng-template pTemplate="body" let-row="rowData">
<button *ngIf="!row.isEdit" (click)="row.isEdit=true">Delete</button>
<button *ngIf="row.isEdit" (click)="save(row)">Save</button>
</ng-template>
</p-column>
<p-column field="brand" header="brand">
<ng-template pTemplate="body" let-col let-row="rowData">
<span *ngIf="!row.isEdit">{{row[col.field]}}</span>
<input type="text" *ngIf="row.isEdit" pInputText [(ngModel)]="row[col.field]">
</ng-template>
</p-column>
</p-dataTable>

編輯highlight

那如果說有需要在編輯的模式中,讓row是呈現被選取的樣式,可以使用rowStyleClass,可以透過這個方法來指定class

1
2
3
4
5
6
@Component()
export class MyComponent {
checkHighlight(row){
return row.isEdit ? 'highlight' : '';
}
}

目前頁的rowIndex

因為我們會有個需求是要新增資料,但如果用splice到第一筆,但是使用者切換到第二頁,會看不到可以編輯的那筆row,翻了文件才發現有這個屬性first,這個的值是這頁的第一筆index

依照剛剛的案例,這時候first會等於10,這時候就可以有兩種作法,第一種是把新的資料直接insert到10的位置,或者是把first改成0,在這邊我是使用第一個方法

一開始要指定first為0,不然會沒資料

Lazy

在一個feature中,我不小心加上了這個設定,然後資料明明已經寫入超過一頁的量,但畫面永遠只顯示一頁(就爆炸了TAT)

去看了官方文件才發現原來是誤會他的功能,這是用來解決大量資料時透過api來取得分頁資料的功能

以這個範例來看,要使用lazy的功能就必須要套用三個屬性/方法lazytotalRecordsonLazyLoad,透過這樣的搭配來取得每頁的資料

1
2
3
4
5
6
7
8
<p-dataTable [value]="cars" [lazy]="true" [rows]="10" [paginator]="true" [rowsPerPageOptions]="[5,10,20]"
[totalRecords]="totalRecords" (onLazyLoad)="loadCarsLazy($event)">
<p-header>List of Cars</p-header>
<p-column field="vin" header="Vin"></p-column>
<p-column field="year" header="Year"></p-column>
<p-column field="brand" header="Brand"></p-column>
<p-column field="color" header="Color"></p-column>
</p-dataTable>

2018/05/23 做後續補充…拖有點久QQ

Selection

同事回報的問題,假設目前selectedList有三筆資料,現在重新取得modelselectedList是不會被清除的,而且還會比對不到任何資料所以就不會被選取,因為這時候的物件和前一次不同,

這個行為我不是很確定算不算問題

1
2
<p-dataTable [value]="model" [(selection)]="selectedList">
</p-dataTable>

Scroll

想要做到固定表頭或是固定前面欄位,可以使用這個功能,但是在設定的時候,發現很容易整個崩壞跑版,尤其是有用到group的功能情況下

目前知道必須要寫死欄位寬高才有辦法比較正常顯示,如果是希望能夠根據資料自動長大的話就…

Dropdown

1
<p-dropdown [options]="reasonList" [autoWidth]="false" placeholder="請選擇" appendTo="body" formControlName="versionReason"></p-dropdown>

如果資料是透過api取得,這時候因為option還沒有任何東西,所以寬度會變得很小要等到點了下拉選單才會變寬,所以要加上autoWidth=false就可以避免寬度自動縮放。

另外,如果是把dropdown放在dialog裡面,而且下拉選單會超過dialog大小,這時候會發現下拉選單被吃掉了!這時候要加上appendTo="body"這個設定,讓下拉選單的z-index提高

結論

其實primeng已經可以符合大部分的使用情境,只是有時候官方文件也許寫的沒有很清楚,那跑去看source code(open source 好處XD)或論壇會比較快

primeng團隊也持續的在開發新元件,也不太需要怕沒有人維護,有什麼好的想法也能提供給他們,一起來讓這個元件更好吧!

讀書會分享時,大家的回響讓我勾起回憶,再補上一些

2018/05/23 再補上一些內容