title: Angular 指令 & 資料繫結
date: 2017-09-12 10:00
categories: Training
keywords:
在 Angular UI:Clarity Design System 中,透過修改 tslint.json
檔的 component-selector
屬性來讓我們可以自訂元件 selector
屬性,現在同樣的來調整 first-app 專案,將 employee 資料夾內的元件改成 emp
開頭,將 home 資料夾內的元件改成 home
開頭。
修改 tslint.json
的 component-selector
設定。
調整相關元件的 selector
屬性。
{% codeblock lang:ts %}
// src\app\employee\address-book\address-book.component.ts
@Component({
selector: ‘emp-address-book’,
…
// src\app\employee\calendar\calendar.component.ts
@Component({
selector: ‘emp-calendar’,
…
// src\app\employee\file\file.component.ts
@Component({
selector: ‘emp-file’,
…
// src\app\employee\leave\leave.component.ts
@Component({
selector: ‘emp-leave’,
…
// src\app\employee\logbook\logbook.component.ts
@Component({
selector: ‘emp-logbook’,
…
// src\app\employee\reimburse\reimburse.component.ts
@Component({
selector: ‘emp-reimburse’,
…
// src\app\employee\to-do-list\to-do-list.component.ts
@Component({
selector: ‘emp-to-do-list’,
…
// src\app\home\aside\aside.component.ts
@Component({
selector: ‘home-aside’,
…
// src\app\home\header\header.component.ts
@Component({
selector: ‘home-header’,
…
// src\app\home\home.component.ts
@Component({
selector: ‘home’,
…
{% endcodeblock %}
修改 src\app\home\home.component.html
,將 app-header
改為 home-header
,app-aside
改為 home-aside
。
{% codeblock home.component.html lang:html %}
{% endcodeblock %}
其實網路上已經有一些 Angular 版本的元件可以套用,不過這邊我們練習如何用簡單的方式就可以做出一個雛形。
開啟 src\app\custom-material.module.ts
加入 MdCardModule。
{% codeblock custom-material.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { FlexLayoutModule } from ‘@angular/flex-layout’;
import {
MdIconModule,
MdButtonModule,
MdListModule,
MdToolbarModule,
MdTooltipModule,
MdSidenavModule,
MdCardModule
} from ‘@angular/material’;
@NgModule({
imports: [
FlexLayoutModule,
MdIconModule,
MdButtonModule,
MdListModule,
MdToolbarModule,
MdTooltipModule,
MdSidenavModule,
MdCardModule
],
exports: [
FlexLayoutModule,
MdIconModule,
MdButtonModule,
MdListModule,
MdToolbarModule,
MdTooltipModule,
MdSidenavModule,
MdCardModule
]
})
export class CustomMaterialModule { }
{% endcodeblock %}
開啟 src\app\employee\employee.module.ts
,將 CustomMaterialModule 註冊到裡面。
{% codeblock employee.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { CommonModule } from ‘@angular/common’;
import { CalendarComponent } from ‘./calendar/calendar.component’;
import { AddressBookComponent } from ‘./address-book/address-book.component’;
import { LogbookComponent } from ‘./logbook/logbook.component’;
import { ToDoListComponent } from ‘./to-do-list/to-do-list.component’;
import { FileComponent } from ‘./file/file.component’;
import { LeaveComponent } from ‘./leave/leave.component’;
import { ReimburseComponent } from ‘./reimburse/reimburse.component’;
import { EmployeeRoutingModule } from ‘./employee-routing.module’;
import { CustomMaterialModule } from ‘../custom-material.module’;
@NgModule({
imports: [
CommonModule,
EmployeeRoutingModule,
CustomMaterialModule
],
declarations: [
CalendarComponent,
AddressBookComponent,
LogbookComponent,
ToDoListComponent,
FileComponent,
LeaveComponent,
ReimburseComponent
]
})
export class EmployeeModule { }
{% endcodeblock %}
開啟 src\app\employee\calendar\calendar.component.html
,加入 card 元件,以及月份切換按鈕。
{% codeblock calendar.component.html lang:html %}
2017 年 9 月
{% endcodeblock %}
執行專案,從瀏覽器上可以看到最外層的卡片效果。
在 Angular 事件 中,我們學會 事件繫結,透過 小括號(( event )
) 的方式讓前端樣板狀態變更時可以通知後端元件。
接下來我們希望畫面上的月份可以透過程式動態修改,所以我們利用 Angular 提供的另一種資料繫結模式-內嵌繫結 (interpolation),我們可以透過2個大括號({{ variable }}
)方式將元件所對應 ts 檔的變數嵌入到樣板上。
若是私有(private)變數則無法存取,若變數沒有加上存取修飾詞,那預設值就是 public。
開啟 src\app\employee\calendar\calendar.component.ts
,加入變數 selectedMonth
,並提供方法將日期轉成文字格式給 selectedMonth
。
{% codeblock calendar.component.ts lang:ts %}
import { Component, OnInit } from ‘@angular/core’;
@Component({
selector: ‘emp-calendar’,
templateUrl: ‘./calendar.component.html’,
styleUrls: [‘./calendar.component.scss’]
})
export class CalendarComponent implements OnInit {
selectedDay: Date;
selectedMonth = ‘’;
constructor() { }
ngOnInit() {
this.getToday();
}
getToday() {
this.selectedDay = new Date();
this.getDay();
}
getDay() {
let year = this.selectedDay.getFullYear();
let month = this.selectedDay.getMonth();
month++;
// month為13時表示隔年的1月。
if (month === 13) {
month = 1;
year++;
}
this.selectedMonth = ${year} 年 ${month} 月
;
}
}
{% endcodeblock %}
TypeScript 在宣告變數時必須給定型別,例如:boolean、number、string、any、…,格式是在變數後面加上冒號(
:
)再加上型別,但是如果有給初始值時可以省略,因為 TypeScript 會自動依初始值來推斷變數的型別,後續程式邏輯若給予的值與型別不符時就會出現警告。
TypeScript 最終還是會編譯成 JavaScript,它主要是提供了開發時期的型別檢查。any
算是比較特殊的型別,代表它可以是任意型別,當變數設成any
時,我們可以視為 TypeScript 就不會做檢查,當變數的值可能為不同型別時可以使用,但是宣告成any
也代表失去 TypeScript 的檢核機制,建議盡量避免使用。
在 function 內宣告變數時,TypeScript 預設會建議你用const
宣告,如果值會改變時則用let
。
修改 calendar.component.html
,套用 selectedMonth
變數。
{% codeblock calendar.component.html lang:html %}
{ {selectedMonth} }
{% endcodeblock %}
從瀏覽器上看不出改變,但是月份已經用變數取代。
修改 calendar.component.html
,為切換按鈕加入事件,並透過 事件繫結 綁定。
{% codeblock calendar.component.html lang:html %}
{ {selectedMonth} }
<button md-button (click)=”getDay(-1)”>上個月
<button md-button (click)=”getToday()”>本月
<button md-button (click)=”getDay(1)”>下個月
{% endcodeblock %}
修改 calendar.component.ts
實作對應的方法。
{% codeblock calendar.component.ts lang:ts %}
import { Component, OnInit } from ‘@angular/core’;
@Component({
selector: ‘emp-calendar’,
templateUrl: ‘./calendar.component.html’,
styleUrls: [‘./calendar.component.scss’]
})
export class CalendarComponent implements OnInit {
selectedDay: Date;
selectedMonth = ‘’;
constructor() { }
ngOnInit() {
this.getToday();
}
getToday() {
this.selectedDay = new Date();
this.getDay(0);
}
getDay(addDMonth: number) {
let year = this.selectedDay.getFullYear();
let month = this.selectedDay.getMonth() + addDMonth;
const dt = new Date(year, month, 1);
year = dt.getFullYear();
month = dt.getMonth();
this.selectedDay = new Date(year, month, 1);
month++;
// month為13時表示隔年的1月。
if (month === 13) {
month = 1;
year++;
}
this.selectedMonth = `${year} 年 ${month} 月`;
}
}
{% endcodeblock %}
ngOnInit
是 Angular 生命週期 衍生的事件,它會發生在第一次繫結資料發生變動(ngOnChanges)之後。
從瀏覽器來看,月份切換已經有效果了
開啟 src\app\custom-material.module.ts
加入 MdGridListModule。
{% codeblock custom-material.module.ts lang:ts %}
import { NgModule } from ‘@angular/core’;
import { FlexLayoutModule } from ‘@angular/flex-layout’;
import {
MdIconModule,
…
MdCardModule,
MdGridListModule
} from ‘@angular/material’;
@NgModule({
imports: [
FlexLayoutModule,
…
MdCardModule,
MdGridListModule
],
exports: [
FlexLayoutModule,
…
MdCardModule,
MdGridListModule
]
})
export class CustomMaterialModule { }
{% endcodeblock %}
修改 calendar.component.html
,加入 Grid List。
{% codeblock calendar.component.html lang:html %}
{ {selectedMonth} }
<button md-button (click)=”getDay(-1)”>上個月
<button md-button (click)=”getToday()”>本月
<button md-button (click)=”getDay(1)”>下個月
{% endcodeblock %}
從瀏覽器來看,行事曆的效果已經出現了
從API文件來看,MdGridListModule 與 HTML 的 Table 功能類似。
MdGridList 提供一個cols
的屬性,當內容項目-MdGridTile 超過其數值時就會自動換行,我們設定7
是因為一個禮拜有7天。
MdGridTile 則提供rowspan
、colspan
可以設定所占用的 列數 與 欄位數,這邊因為都是占用1單位,所以不設定。
Angular 提供一個樣板指令-*ngFor,讓我們可以依照陣列物件重複產生樣板語法,同時將資料嵌入到樣板內。
修改 calendar.component.ts
,加入星期的陣列變數-dayHeaders。
{% codeblock calendar.component.ts lang:ts %}
import { Component, OnInit } from ‘@angular/core’;
@Component({
selector: ‘emp-calendar’,
templateUrl: ‘./calendar.component.html’,
styleUrls: [‘./calendar.component.scss’]
})
export class CalendarComponent implements OnInit {
selectedDay: Date;
selectedMonth = ‘’;
dayHeaders = [‘日’, ‘一’, ‘二’, ‘三’, ‘四’, ‘五’, ‘六’];
constructor() { }
…
{% endcodeblock %}
修改 calendar.component.html
,透過 ngFor 指令來巡覽 dayHeaders
陣列,並宣告一個 變數(header
) 來承接其值,最後再透過*內嵌繫結**將 header
嵌入到樣板內。
{% codeblock calendar.component.html lang:html %}
{ {selectedMonth} }
<md-grid-tile *ngFor=”let header of dayHeaders”>
{ {header} }
…
{% endcodeblock %}
從瀏覽器上看不出改變,但是星期已經用變數取代。
*ngFor
本身還提供下列變數可使用:index
(number):表示目前資料在陣列中的索引值。first
(boolean):表示目前資料是否為第一筆。last
(boolean):表示目前資料是否為最後一筆。even
(boolean):表示目前資料的索引值是否為偶數筆。odd
(boolean):表示目前資料的索引值是否為奇數筆。
但是上述變數不可直接使用,必須另行宣告變數來承接。
例如:我們宣告一個變數i
來承接index
索引值,並將i
嵌入到星期變數前面。
Angular 提供一個類似 style
tag 的指令-[ngStyle]
,最大的差異就是 [ngStyle]
所設定CSS的屬性值可以是變數、方法,也就是我們可以動態改變值。
修改 calendar.component.ts
,加入星期的顏色陣列變數-dayColors。
{% codeblock calendar.component.ts lang:ts %}
import { Component, OnInit } from ‘@angular/core’;
@Component({
selector: ‘emp-calendar’,
templateUrl: ‘./calendar.component.html’,
styleUrls: [‘./calendar.component.scss’]
})
export class CalendarComponent implements OnInit {
selectedDay: Date;
selectedMonth = ‘’;
dayHeaders = [‘日’, ‘一’, ‘二’, ‘三’, ‘四’, ‘五’, ‘六’];
dayColors = [‘red’, ‘white’, ‘white’, ‘white’, ‘white’, ‘white’, ‘green’];
constructor() { }
…
{% endcodeblock %}
開啟 calendar.component.html
,加入 ngStyle
將文字顏色來源指向 dayColors。
{% codeblock calendar.component.html lang:html %}
{ {selectedMonth} }
<md-grid-tile *ngFor=”let header of dayHeaders; let i = index”
[ngStyle]=”{‘color’: dayColors[i]}”>
{ {header} }
…
{% endcodeblock %}
從瀏覽器上可以看到星期日變成紅色、星期六變成綠色。
接下來就是將日期改為動態產生,開啟 calendar.component.ts
,新增 days
陣列變數,在 getDay(addDMonth)
方法內將選取月份的日期與星期都填入 days
內。
{% codeblock calendar.component.ts lang:ts %}
…
export class CalendarComponent implements OnInit {
selectedDay: Date;
selectedMonth = ‘’;
dayHeaders = [‘日’, ‘一’, ‘二’, ‘三’, ‘四’, ‘五’, ‘六’];
dayColors = [‘red’, ‘white’, ‘white’, ‘white’, ‘white’, ‘white’, ‘green’];
days = [];
constructor() { }
…
getDay(addDMonth: number) {
let year = this.selectedDay.getFullYear();
let month = this.selectedDay.getMonth() + addDMonth;
const dt = new Date(year, month, 1);
year = dt.getFullYear();
month = dt.getMonth();
const _days = [];
for (let day = 1; day <= 31; day++) {
const time = new Date(year, month, day);
if (time.getMonth() > month) {
break;
}
const d: any = {
day: day,
week: time.getDay()
};
_days.push(d);
}
this.days = [..._days];
this.selectedDay = new Date(year, month, 1);
month++;
// month為13時表示隔年的1月。
if (month === 13) {
month = 1;
year++;
}
this.selectedMonth = `${year} 年 ${month} 月`;
}
}
{% endcodeblock %}
開啟 calendar.component.html
,加入 ngStyle
將文字顏色來源指向 dayColors。
{% codeblock calendar.component.html lang:html %}
…
<md-grid-tile *ngFor=”let header of dayHeaders; let i = index”
[ngStyle]=”{‘color’: dayColors[i]}”>
{{header}}
<md-grid-tile *ngFor=”let item of days”
[ngStyle]=”{‘color’: dayColors[item.week]}”>
{{item.day}}
…
{% endcodeblock %}
從瀏覽器上可以看到日期為星期日與星期六的顏色都改變了,但是位置不對,2017/9/1 應該是禮拜五。
上面提到的 內嵌繫結 可以將後端元件類別變數輸出到前端樣板,但是它最終會轉換成文字,如果我們需要繫結對象是樣板元素的屬性時可能就會不適用,因此 Angular 提供另一種資料繫結模式-屬性繫結,我們可以在目標屬性上加上 中括號([ property ]
) 來表示資料來源是來自元件類別。
屬性繫結 的資料如果是字串,可以在值外面加上單引號來表示值為字串。
我們在每月1號的 md-grid-tile
前面再插入一個 md-grid-tile
,並透過 colspan
屬性將1號推擠到對應的星期。
開啟 calendar.component.ts
,新增 lastMonth_colspan
變數,在 getDay(addDMonth)
方法內計算需要偏移的天數。
{% codeblock calendar.component.ts lang:ts %}
…
export class CalendarComponent implements OnInit {
selectedDay: Date;
selectedMonth = ‘’;
dayHeaders = [‘日’, ‘一’, ‘二’, ‘三’, ‘四’, ‘五’, ‘六’];
dayColors = [‘red’, ‘white’, ‘white’, ‘white’, ‘white’, ‘white’, ‘green’];
days = [];
lastMonth_colspan = 0;
constructor() { }
…
getDay(addDMonth: number) {
let year = this.selectedDay.getFullYear();
let month = this.selectedDay.getMonth() + addDMonth;
const dt = new Date(year, month, 1);
year = dt.getFullYear();
month = dt.getMonth();
this.lastMonth_colspan = new Date(year, month, 1).getDay();
const _days = [];
...
{% endcodeblock %}
開啟 calendar.component.html
,加入一個 md-grid-tile
,並透過屬性繫結將 colspan
屬性繫結至 lastMonth_colspan
變數。
{% codeblock calendar.component.html lang:html %}
…
<md-grid-tile *ngFor=”let header of dayHeaders; let i = index”
[ngStyle]=”{‘color’: dayColors[i]}”>
{{header}}
<md-grid-tile [colspan]=”lastMonth_colspan”>
<md-grid-tile *ngFor=”let item of days”
[ngStyle]=”{‘color’: dayColors[item.week]}”>
{{item.day}}
…
{% endcodeblock %}
從瀏覽器上看起來應該都是正常。
我們切換到下個月(2017/10)問題就出現了,原本 2017/10/1 應該是星期日,但是卻被移到星期六。
當 lastMonth_colspan = 0
時表示不需要推移,因此應該移除日期前的 md-grid-tile
,Angular 提供一個便捷的樣板指令-*ngFor,當後面的判斷式為 true
時該樣板元素會顯示,當值為 false
時該樣板元素會被移除。
開啟 calendar.component.html
,添加 *ngFor 判斷式。
{% codeblock calendar.component.html lang:html %}
…
<md-grid-tile *ngFor=”let header of dayHeaders; let i = index”
[ngStyle]=”{‘color’: dayColors[i]}”>
{{header}}
<md-grid-tile [colspan]=”lastMonth_colspan” *ngIf=”lastMonth_colspan”>
…
{% endcodeblock %}
從瀏覽器上看起來 2017/10 月份行事曆應該就正常。
最後我們在 src\app\employee\calendar\calendar.component.scss
加上一點 CSS 樣式,並套用到 calendar.component.html
。
{% codeblock calendar.component.scss lang:scss %}
.day {
border: solid 1px DimGray;
.text {
position: absolute;
top: 4px;
right: 8px;
}
}
{% endcodeblock %}
{% codeblock calendar.component.html lang:html %}
{{selectedMonth}}
<md-grid-tile *ngFor=”let header of dayHeaders; let i = index”
[ngStyle]=”{‘color’: dayColors[i]}”>
{{header}}
<md-grid-tile [colspan]=”lastMonth_colspan” *ngIf=”lastMonth_colspan”>
<md-grid-tile *ngFor=”let item of days”
[ngStyle]=”{‘color’: dayColors[item.week]}” class=”day”>
{{item.day}}
<button md-button (click)=”getDay(-1)”>上個月
<button md-button (click)=”getToday()”>本月
<button md-button (click)=”getDay(1)”>下個月
{% endcodeblock %}
執行專案,從瀏覽器上操作應該都正常了。
{% img /images/download.png 36 %}first-app_2017-09-12.zip