
Ng-Matero 0.3 已發(fā)布,新增 module schematic 以及 page schematic,詳見(jiàn) README
前言
Angular Material 作為 Angular 的官方組件庫(kù),無(wú)論是設(shè)計(jì)交互還是易用性都有著極高的質(zhì)量。正如官方所說(shuō)其目的就是構(gòu)建基于 Angular 和 TypeScript 的高質(zhì)量組件庫(kù)。
官方列舉了如下幾點(diǎn)來(lái)解釋“高質(zhì)量”的含義。
- 國(guó)際化和可訪問(wèn)性,以便所有用戶都可以使用。
- 不會(huì)讓開(kāi)發(fā)人員感到困惑的簡(jiǎn)單 API。
- 在各種各樣沒(méi)有 bug 的用例中按預(yù)期行事。
- 通過(guò)單元測(cè)試和集成測(cè)試更好地測(cè)試行為。
- 可在 Material Design 規(guī)范的范圍內(nèi)進(jìn)行定制。
- 將性能開(kāi)銷降至最低。
- 代碼簡(jiǎn)潔,文檔友好,可以作為 Angular 開(kāi)發(fā)人員的一個(gè)例子。
Material Design 作為一個(gè)非常流行的設(shè)計(jì)語(yǔ)言,它有多個(gè)版本的實(shí)現(xiàn)。React 版的 Material Design 有著很高的人氣,大家可以自行對(duì)比,我就不贅述了,以免引起無(wú)謂的爭(zhēng)吵,進(jìn)而扯到框架層面。我可以說(shuō)一下自己的感受,Angular Material 的交互更加流暢,細(xì)節(jié)做的更好。
Angular Material 組件庫(kù)雖然很優(yōu)秀,但是卻被戴上了只適合做 C 端界面的帽子。這也是我剛開(kāi)始不敢選擇 Angular Material 的一個(gè)原因。但是在編寫 ng-matero 的過(guò)程中,隨著對(duì) Angular Material 的深入了解,我發(fā)現(xiàn)這種說(shuō)法稍顯狹隘甚至產(chǎn)生了一定的誤導(dǎo),所以我希望這篇文章可以讓大家對(duì) Angular Material 有一個(gè)更加正確的認(rèn)識(shí)。接下來(lái)我會(huì)從相對(duì)宏觀的角度介紹 Angular Material 設(shè)計(jì)的一些亮點(diǎn),并且簡(jiǎn)單介紹 Angular Material 的一些使用技巧。
題外話:為什么 ng-matero 會(huì)選擇 Angular Material?
拋開(kāi)官方提到的幾點(diǎn)不談。首先我是那種比較激進(jìn)的開(kāi)發(fā)者,對(duì)于先進(jìn)的設(shè)計(jì)理念,我都有躍躍欲試的執(zhí)念。國(guó)內(nèi)的 Element UI 以及 Ant Design 都是 Bootstrap 3 時(shí)代的風(fēng)格。隨著業(yè)務(wù)人員對(duì)界面細(xì)致緊湊的要求越來(lái)越高,我發(fā)現(xiàn) Material 的設(shè)計(jì)風(fēng)格更加符合需求,層次感更強(qiáng)。不過(guò)最主要的還是 Material Design 的交互更吸引我。另外,Angular Material 的樣式是基于 Sass 編寫,而我最喜歡的也是 Sass,所以基于 Angular Material 編寫 ng-matero 就是宿命的選擇。順便插一句,如果大家糾結(jié)用 Sass 還是 Less,可以看一下這篇文章 CSS 預(yù)處理器中的循環(huán),個(gè)人不建議用 Less,請(qǐng)?jiān)徫覠o(wú)意引戰(zhàn)??。
少即是多
Less is more(少即是多)—— 密斯·凡德羅
我想很多人對(duì) Angular Material 望而卻步的原因之一就是它的組件看上去有點(diǎn)少。然而在一般的業(yè)務(wù)中這些組件已經(jīng)夠用。除了常用組件之外,Angular Material 還有一個(gè)組件開(kāi)發(fā)包 CDK。在設(shè)計(jì)界有一句名言“少即是多”,蘋果的產(chǎn)品就是最好的證明。把這句名言用在 Angular Material 上絲毫不為過(guò),其實(shí)除了我們看到的組件之外,Material 還有一些隱藏組件,比如可以用 menu 組件構(gòu)造 popover,我會(huì)在下文中介紹。
豐富的顏色
Material Design 的亮點(diǎn)之一就是擁有非常豐富的顏色值,其實(shí) Angular Material 的顏色變量比官方定義的色值還要多一些。大家可以點(diǎn)擊 ng-matero 的 colors 頁(yè)面 查看。ng-matero 也有所有顏色值對(duì)應(yīng)的 colors helper,可以更加方便的創(chuàng)建豐富多彩的按鈕或標(biāo)簽。Angular Material 的顏色定義嚴(yán)謹(jǐn)且優(yōu)雅。以下是紅色值的變量。
$mat-red: (
50: #ffebee,
100: #ffcdd2,
200: #ef9a9a,
300: #e57373,
400: #ef5350,
500: #f44336,
600: #e53935,
700: #d32f2f,
800: #c62828,
900: #b71c1c,
A100: #ff8a80,
A200: #ff5252,
A400: #ff1744,
A700: #d50000,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
400: $dark-primary-text,
500: $light-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $light-primary-text,
A400: $light-primary-text,
A700: $light-primary-text,
)
);
除了定義基礎(chǔ)色值之外,還有相對(duì)應(yīng)的文本色定義,非常嚴(yán)謹(jǐn)。我在以前寫 helper 庫(kù) 的時(shí)候,曾寫過(guò)顏色集群,文本色處理都是一刀切,非常不嚴(yán)謹(jǐn),所以感觸非常深。更驚喜的的是 Angular Material 甚至給出了灰色值的別名。
// Alias for alternate spelling.
$mat-gray: $mat-grey;
靈活的主題定制
Angular Material 的樣式幾乎全部寫在了 mixin 中,定制起來(lái)非常容易。我最開(kāi)始認(rèn)為將所有樣式全部寫到 mixin 中并不是很優(yōu)雅的做法,但是在編寫 ng-matero 暗黑主題的時(shí)候,我發(fā)現(xiàn)不這樣做是不行的。以下是 Angular Material 主題定制的方法。
@import '~@angular/material/theming';
// Include non-theme styles for core.
@include mat-core();
// Define a theme.
$candy-app-primary: mat-palette($mat-indigo);
$candy-app-accent: mat-palette($mat-pink, A200, A100, A400);
$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);
// Include all theme styles for the components.
@include angular-material-theme($candy-app-theme);
Angular Material 給出了多套主題的設(shè)置方法,只需要增加樣式控制類就可以了。
// Define an alternate dark theme.
$dark-primary: mat-palette($mat-blue-grey);
$dark-accent: mat-palette($mat-amber, A200, A100, A400);
$dark-warn: mat-palette($mat-deep-orange);
$dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
// `.unicorn-dark-theme` will be affected by this alternate dark theme instead of the default theme.
.unicorn-dark-theme {
@include angular-material-theme($dark-theme);
}
在此我簡(jiǎn)單介紹一下 ng-matero 的主題切換。增加樣式控制類可以說(shuō)是最簡(jiǎn)單的主題切換方式,但是缺點(diǎn)就是同時(shí)擁有多套主題,代碼量太大。如果只作為 DEMO 展示是沒(méi)問(wèn)題的,但是生產(chǎn)環(huán)境不推薦這樣做。
ng-matero 在使用 ng add 初始化的時(shí)候增加了預(yù)構(gòu)建主題選項(xiàng),生成的主題只有一份,如果有特殊需求可以自行定制。實(shí)現(xiàn)方式就是不同主題傳入不同變量,但是這種情況下多主題控制會(huì)有問(wèn)題。所以必須使用 mixin 編寫某些樣式,這樣的話就可以有局部變量環(huán)境。如下:
.theme-dark {
$primary: mat-palette($mat-pink, 700, 500, 900);
$accent: mat-palette($mat-blue-grey, A200, A100, A400);
$warn: mat-palette($mat-red);
$theme: mat-dark-theme($primary, $accent, $warn);
@include angular-material-theme($theme);
@include matero-admin-theme($theme);
}
工具集
Angular Material 提供了幾乎所有和 Material Design 有關(guān)的樣式工具,包括變量、 function 和 mixin,都可以在 theming 文件中找到。
除了上面提到的主題定制 function、mixin 之外,我們還可以使用 mat-elevation() 輕松制作 MD 陰影。另外我們還可以使用 $swift-ease-out-timing-function、$mat-fast-out-slow-in-timing-function 這些動(dòng)畫變量實(shí)現(xiàn)和 MD 一樣的動(dòng)畫效果。
基于這套工具集,我們可以很容易的搭建和 MD 風(fēng)格相統(tǒng)一的界面。
極簡(jiǎn)的 API
Angular Material 的官方文檔可能稍微不太友好,總感覺(jué)內(nèi)容很多,看不進(jìn)去。但是耐心看一下,就會(huì)發(fā)現(xiàn)其簡(jiǎn)潔之道,Angular Material 的 API 也是“少即是多”的一種表現(xiàn)。以表單組件為例,以下是一個(gè)滑塊組件。
<mat-slide-toggle [(ngModel)]="options.model"
(change)="changeOptions()"
[disabled]="options.disabled">visible
</mat-slide-toggle>
Angular Material 的表單組件更像是對(duì)原生 html 元素的復(fù)寫。在熟悉了一種組件之后,幾乎不需要額外的記憶成本,就可以很容易的猜到某些 API,簡(jiǎn)單易懂,使用很方便。不過(guò)時(shí)常翻文檔還是很有必要的。
再看一下菜單組件,使用方式同樣非常簡(jiǎn)單。
<button mat-button [matMenuTriggerFor]="menu">Menu</button>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>
在我更新 ng-zorro-antd 8.x 之后,我發(fā)現(xiàn) zorro 的菜單組件的使用已經(jīng)和 Angular Material 一樣了??梢?jiàn)優(yōu)秀的設(shè)計(jì)理念會(huì)被廣泛借鑒。
菜單
Angular Material 的菜單組件可以說(shuō)非常強(qiáng)大,除了官網(wǎng)提到的功能之外,我們還可以用以下方式實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)加載的多級(jí)菜單,比如 ng-matero 的 Top Menu 布局。
<a mat-button [routerLink]="['/', menuItem.state]" *ngIf="menuItem.type === 'link'">
<span>{{menuItem.name}}</span>
...
</a>
...
<!-- level 1 -->
<button mat-button *ngIf="menuItem.type === 'sub'" [matMenuTriggerFor]="menulevel1">
<span>{{menuItem.name}}</span>
...
</button>
<mat-menu #menulevel1="matMenu">
<ng-container *ngFor="let childLvl1 of menuItem.children">
<a mat-menu-item [routerLink]="['/', menuItem.state, childLvl1.state]"
*ngIf="childLvl1.type === 'link'">{{childLvl1.name}}</a>
...
<!-- level 2 -->
<button mat-menu-item *ngIf="childLvl1.type === 'sub'"
[matMenuTriggerFor]="menulevel2">{{ childLvl1.name }}</button>
<mat-menu #menulevel2="matMenu">
<ng-container *ngFor="let childLvl2 of childLvl1.children">
<a mat-menu-item
[routerLink]="filterStates(['/', menuItem.state, childLvl1.state, childLvl2.state])"
*ngIf="childLvl2.type === 'link'">{{childLvl2.name}}</a>
...
</ng-container>
</mat-menu>
</ng-container>
</mat-menu>
另外,菜單組件還可以實(shí)現(xiàn) popover 的效果,不過(guò)需要做一些特殊處理,如下:
<mat-menu class="menu-form-wrapper" [hasBackdrop]="false">
<div (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()">
...
</div>
</mat-menu>
最后可以根據(jù)自己的需求調(diào)整一下樣式。
表格
Angular Material 的表格是我見(jiàn)過(guò)最特殊的表格,結(jié)構(gòu)簡(jiǎn)潔,通過(guò)定義動(dòng)態(tài)列渲染數(shù)據(jù),以下是一個(gè)官網(wǎng)例子:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<!-- Position Column -->
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef> No. </th>
<td mat-cell *matCellDef="let element"> {{element.position}} </td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef> Weight </th>
<td mat-cell *matCellDef="let element"> {{element.weight}} </td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
<th mat-header-cell *matHeaderCellDef> Symbol </th>
<td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
相比于 ng-zorro-antd 會(huì)暴露全部的 DOM 結(jié)構(gòu),這種簡(jiǎn)潔的結(jié)構(gòu)(CDKTable 的結(jié)構(gòu)也是如此)確實(shí)讓人不適應(yīng),甚至有一些擔(dān)憂,遇到復(fù)雜的需求會(huì)不會(huì)吃癟。在我寫了大量表格需求之后,我可以很肯定地說(shuō) Angular Material 的表格足以應(yīng)對(duì)復(fù)雜需求(話也不敢說(shuō)太滿??)。
我很贊同 ng-alain 對(duì) ng-zorro-antd 表格的進(jìn)一步抽象,熟悉了 ng-alain 編寫表格的方式之后,我一直以為 mat-table 略顯笨拙。然而仔細(xì)研究一下就會(huì)發(fā)現(xiàn),mat-table 是在 DOM 層面的抽象,本質(zhì)是一樣的。
mat-table 對(duì)表格列寬的首選操控方式是 CSS,起初我對(duì)這種方式也存在疑慮,但是在我親自封裝了 ng-zorro-antd 的表格組件之后,我發(fā)現(xiàn)一切都很自然。這讓我想起前端流行的一句話,“凡事能用 CSS 完成的就不要用 JS”,這也是我不建議大家用 Less 的原因之一。
ng-matero 的表格示例是最簡(jiǎn)單的業(yè)務(wù)表格,可以參考其實(shí)現(xiàn)方法。
響應(yīng)式布局
Angular Material 并沒(méi)有布局組件。但是不用擔(dān)心,官方出品了一款基于指令布局的神器 flex-layout,它是專門為 Angular 設(shè)計(jì)的?;谥噶畹牟季址绞胶?Bootstrap 的柵格布局是兩種不同的設(shè)計(jì)理念。flex-layout 的使用很簡(jiǎn)單,可以很快上手,熟悉之后你一定會(huì)喜歡這種布局方式。
總結(jié)
文章篇幅有限,以我淺薄的資歷還無(wú)法將 Angular Material 的設(shè)計(jì)之美剖析的面面俱到,但是如果大家通過(guò)這篇文章能夠更好的了解 Angular Material 或者對(duì) Angular Material 產(chǎn)生了一點(diǎn)興趣,我也算是做了一件好事。
任何組件庫(kù)都無(wú)法滿足所有業(yè)務(wù)需求,如果你無(wú)法在 Angular Material 中找到可用的組件,你可以嘗試第三方組件,或者可以將 ng-zorro-antd 按模塊單獨(dú)引入。在此推薦一些優(yōu)秀的第三方組件。
如果大家喜歡 Angular 或者對(duì) Angular Material 感興趣,歡迎進(jìn)群討論!