昨天我們把一個data table的基礎功能-「顯示資料、分頁、排序」都大致說明了一遍,今天我們來講一些進階的data table用法,以及分頁和排序元件的補充說明;Angular Material中的分頁和排序功能都很強,而且也不會和<mat-table>
綁死,在任何地方可以應用。
就讓我們繼續往下看吧!
要篩選data table的資料,在前後端都不算困難,我們來看看該如何實作。
要篩選前端已經撈出來的資料並不困難,只需要為提供的data source設定filter
屬性即可。
我們先在畫面上加入一個輸入框:
<mat-form-field>
<input matInput #filter placeholder="搜尋">
</mat-form-field>
之後在程式中針對input變化時設定data source的filter:
export class EmailListComponent implements OnInit {
@ViewChild('filter') filter: ElementRef;
ngOnInit() {
Observable.fromEvent(this.filter.nativeElement, 'keyup')
.debounceTime(300)
.distinctUntilChanged()
.subscribe(() => {
this.emailsDataSource.filter = (this.filter.nativeElement as HTMLInputElement).value;
});
}
}
成果如下:
剛才我們使用filter
讓data source自己來過濾資料,不過這樣會有點問題,最主要是filter會篩選所有的欄位,只要有某個欄位包含filter
內容,就會取出結果,但這未必是我們要的,以上面的例子來說,只看得到標題,卻過濾出一些標題不包含filter
的資訊,依情境而定有可能會造成誤解。
Angular Material的MatTableDataSource
中有一個filterPredicate
方法,是用來對filter
篩選資料用的,我們可以複寫這個方法,來針對需要的欄位篩選就好:
this.emailsDataSource.filterPredicate = (data: any, filter: string): boolean => {
return data.title.indexOf(filter) !== -1;
};
這個方法有兩個傳入參數,分別是每列的資料物件(data
),以及要篩選的內容(filter
),我們可以回傳true代表這筆資料要顯示,反之則不顯示。
再來看看結果:
這時候就能夠只針對標題的欄位篩選囉。
關於後端篩選,就更加簡單了,不需要再使用data source的filter
,只需要把篩選資料送給API就好:
@Component({
selector: 'app-email-list',
templateUrl: './email-list.component.html',
styleUrls: ['./email-list.component.css']
})
export class EmailListComponent implements OnInit {
...
@ViewChild('filter') filter: ElementRef;
currentFilterData: string;
ngOnInit() {
...
Observable.fromEvent(this.filter.nativeElement, 'keyup')
.debounceTime(300)
.distinctUntilChanged()
.subscribe((filterData: string) => {
// 準備要提供給API的filter資料
this.currentFilterData = filterData;
this.getIssuees();
// 後端篩選就不需要指定filter了
// this.emailsDataSource.filter = (this.filter.nativeElement as HTMLInputElement).value;
});
// 後端篩選就用不到filterPredicate了
// this.emailsDataSource.filterPredicate = (data: any, filter: string): boolean => {
// return data.title.indexOf(filter) !== -1;
// };
}
getIssuees() {
const baseUrl = 'https://api.github.com/search/issues?q=repo:angular/material2';
let targetUrl = `${baseUrl}&page=${this.currentPage.pageIndex + 1}&per_page=${this.currentPage.pageSize}`;
if (this.currentSort.direction) {
targetUrl = `${targetUrl}&sort=${this.currentSort.active}&order=${this.currentSort.direction}`;
}
this.httpClient.get<any>(targetUrl).subscribe(data => {
this.totalCount = data.total_count;
this.emailsDataSource.data = data.items;
});
}
}
成果如下:
從篩選、分頁到排序,3個願望通通都滿足啦!
在之前的範例中,我們都是針對<mat-table>
來進行排序,但其實matSort
和mat-sort-header
在任何地方都使用,只要在外層元素加上matSort
,當內部的mat-sort-header
點擊時,就會發生matSortChange
事件。因此以下程式是完全可以正常運作的!
以下我們使用mat-sort-header="xxxx"
,代表排序時的欄位名稱是title
:
<table matSort (matSortChange)="sort($event)">
<thead>
<tr>
<th mat-sort-header="title">標題</th>
</tr>
</thead>
</table>
只要任何地方有排序的需求,不管是不是<mat-table>
只要加上matSort
和mat-sort-header
就搞定啦!
使用mat-sort-header
會在內容旁邊加上一個箭頭符號,方便我們判別目前的排序狀態,這個符號也是可以調整的,有以下幾個屬性可以設定:
arrowPosition
:箭頭符號要放在文字的前面(before
)還是後面(after
)disableClear
:是否允許取消排序,預設為false
,會以asc
-> desc
-> 無
輪流切換;若設成true
則只會在asc
和desc
之間切換。start
:當按下排序時預設先顯示的排序狀態,預設為asc
,可以選擇asc
或desc
。以下程式會將欄位的排序規則改為,(1)把箭頭放在前面、(2)無法取消排序和(3)預設先以desc
排序:
<mat-header-cell *matHeaderCellDef
mat-sort-header
arrowPosition="before"
disableClear="true"
start="desc">
日期
</mat-header-cell>
結果如下:
可以看到大致上都如我們預期的顯示,唯一的問題是arrowPosition="before"
後,整個內容被推到右邊了,這是因為flex設定的關係,從開發人員工具可以看到header cell的加上了一個mat-sort-header-position-before
樣式如下:
不過這不是大問題,自行CSS調整一下即可:
.mat-sort-header-position-before {
justify-content: flex-end;
}
成果如下:
看起來就正常多啦!
<mat-paginator>
一樣不需要跟<mat-table>
綁死,我們可以在任何需要呈現多筆資料的地方使用<mat-paginator>
,只要設定好至確的參數及可正常顯示,並且得知分頁切換時的事件。
<mat-paginator #paginator
[length]="totalCount"
[pageIndex]="0"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 15]"
(page)="pageChange($event)">
</mat-paginator>
相關的內容在上一篇文章都已經有詳細的說明了,在這邊就不多做說明囉。
在<mat-paginator>
中的文字內容都是英文的,包含上一頁及下一頁按鈕,當滑鼠移過去時會呈現一個tooltip,如下:
當然這個文字我們也可以進行調整,只要在MatPaginatorIntl
這個service裡面設定即可:
@Component({ })
export class EmailListComponent implements OnInit {
constructor(private matPaginatorIntl: MatPaginatorIntl) {}
ngOnInit() {
// 設定顯示筆數資訊文字
this.matPaginatorIntl.getRangeLabel = (page: number, pageSize: number, length: number): string => {
if (length === 0 || pageSize === 0) {
return `第 0 筆、共 ${length} 筆`;
}
length = Math.max(length, 0);
const startIndex = page * pageSize;
const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;
return `第 ${startIndex + 1} - ${endIndex} 筆、共 ${length} 筆`;
};
// 設定其他顯示資訊文字
this.matPaginatorIntl.itemsPerPageLabel = '每頁筆數:';
this.matPaginatorIntl.nextPageLabel = '下一頁';
this.matPaginatorIntl.previousPageLabel = '上一頁';
}
}
成果如下:
所有的文字都變成中文呈現啦!
今天我們介紹了另一個常在data table中常出現的功能,篩選資料(filter),一樣的,不管是直接處理前端的資料還是後端,都不會造成問題;在前端篩選時我們甚至幾乎不用寫什麼程式即可完成篩選,當然我們也能透過自訂filterPredicate
來決定篩選邏輯;在後端資料篩選時則沒有什麼特別,就是傳遞資料給API而已。
另外我們也補充了一些關於分頁及排序元件的使用方法,這些方法都是筆者個人認為比較重要的部分,但實際上則有更多可以調整的部分;例如排序功能其實也有一個MatSortHeaderIntl
可以設定文字,但主要是在螢幕閱讀器的部分使用,比較難用畫面呈現,因此也就沒有特別介紹了。
關於排序、分頁還有許多可以設定的屬性、API等等,可以直接到官方的文件上去看,也有詳細的說明。
本日的程式碼GitHub:https://github.com/wellwind/it-ironman-demo-angular-material/tree/day-24-data-table-advanced
分支:day-24-table-advanced
我們終於把所有Angular Material目前版本(5.0.0)所有的30個主要功能和常見的使用情境都介紹過啦!有沒有覺得非常充實,也對於Angular Material的設計和互動感及考量的全面性感覺到非常佩服啊!?如果有任何問題不明白,或是覺得需要補充的內容,都歡迎你直接在下方留言喔!
明天開始我們要邁入從Angular Material衍伸出來的另一大主題-Angular CDK,讓你不必非得要綁死Angular Material,也能打造出具有互動感的元件,準備好迎接新的挑戰吧!!
您好:
跟你請教一個問題,該如何讓 Data Table 在選擇之後有光棒,另外如設定成可多選
再次感謝您的文章,讓來開發 UI 上的便利
您好:
Angular的Table僅負責將資料呈現出來,不負責其他複雜的呈現效果,但這些效果在Angular中要實作並不是件很困難的事情,只要去綁定click, mouseover, mouseout等事件,並改變mat-row
的樣式即可,我做了以下範例程式給您參考:
https://stackblitz.com/edit/angular-selected-material-table?file=app%2Ftable-basic-example.html
程式碼重點:
<ng-container matColumnDef="checked">
<mat-header-cell *matHeaderCellDef>Check</mat-header-cell>
<mat-cell *matCellDef="let element">
<mat-checkbox [(ngModel)]="element.checked"></mat-checkbox>
</mat-cell>
</ng-container>
<mat-row *matRowDef="let row; columns: displayedColumns;" [ngClass]="{hovered: row.hovered, highlighted: row.highlighted}" (click)="row.highlighted = !row.highlighted" (mouseover)="row.hovered = true" (mouseout)="row.hovered = false"></mat-row>
.mat-row.hovered {
background: #eee;
}
.mat-row.highlighted {
background: #999;
}
/*
This is a work around for checkbox bug in material row
https://github.com/angular/material2/issues/8600
*/
mat-cell.mat-cell, mat-header-cell.mat-header-cell {
overflow: visible;
}
厲害,果然是神人,讓我對 angular material 的應用有更多的想像
thx
順帶一提,Angular Material 6已經正確支援這個功能囉!
https://material.angular.io/components/table/overview#selection
基本原理是一樣的,但提供了完整的資料結構來儲存
您好:
請問以下這段 disableClear="true" 是否能全域設定??
<mat-header-cell *matHeaderCellDef mat-sort-header disableClear="true">
日期
您好, disableClear
是 matSortHeader 的一個 @Input
,所以應該是沒有特別可以全域設定的地方,但您可以自己撰寫一個新的 directive 繼承 matSortHeader,然後覆蓋掉 disableClear
的設定即可
好的,了解! 謝謝您!
你好:
如果資料欄位為數字,就不能 filter 了嗎?
程式碼及錯誤程式碼如下:
observableFromEvent(this.filter.nativeElement, 'keyup').pipe(
debounceTime(300),
distinctUntilChanged())
.subscribe(() => {
this.defectsDataSource.filter = (this.filter.nativeElement as HTMLInputElement).value;
});
this.defectsDataSource.filterPredicate = (data: any, filter: string): boolean => {
return data.BG_BUG_ID.indexOf(filter) !== -1;
}
![https://ithelp.ithome.com.tw/upload/images/20190731/20108814x9m0ssMky5.png](https://ithelp.ithome.com.tw/upload/images/20190731/20108814x9m0ssMky5.png)
數字沒有 indexOf 所以要調整你過濾的條件喔!
例如 BG_BUG_ID > parseInt(filter, 0)
請在依照自己的狀況調整
感謝版主:
回報一下測試的結果,使用你提供的方法會把大於 filter 的資料全部顯示出來,但如果先把 BG_BUG_ID 轉成 string,則就會只要包含輸入內容的資料,
let tt = data.BG_BUG_ID.toString();
if (tt.indexOf(Filter) != -1)
return true;
else
return false;
}
再次謝謝出問題點
BG_BUG_ID === parseInt(filter, 0)
如何?
不好意思,可以連續針對二個欄位,進行 filter 嗎?
observableFromEvent(this.filter01.nativeElement, 'keyup').pipe(
debounceTime(300),
distinctUntilChanged())
.subscribe(() => {
this.defectsDataSource.filter = (this.filter01.nativeElement as HTMLInputElement).value;
});
this.defectsDataSource.filterPredicate = (data: any, Filter01: string): boolean => {
let tt = data.BG_BUG_ID.toString();
if (tt.indexOf(Filter01) != -1)
return true;
else
return false;
}
observableFromEvent(this.filter02.nativeElement, 'keyup').pipe(
debounceTime(300),
distinctUntilChanged())
.subscribe(() => {
this.defectsDataSource.filter = (this.filter02.nativeElement as HTMLInputElement).value;
});
this.defectsDataSource.filterPredicate = (data: any, Filter02: string): boolean => {
if ((data.BG_SUMMARY.indexOf(Filter02) != -1))
return true;
else
return false;
}
試過好只會對最一個 filterPredicate 有作用?
還是我的寫法有問題?
感謝