title: Angular 單向繫結
date: 2017-09-13 10:00
categories: Training
keywords:
我們在 Angular 事件 中演練了 事件繫結 的應用,在 Angular 指令 & 資料繫結 中演練了 內嵌繫結 與 屬性繫結,這3種資料繫結方式正是 Angular 所提供的單向繫結,其差異如下:
當後端類別所繫結的 變數(或屬性) 發生變化時,會自動更新前端樣板的資料。
內嵌繫結 (interpolation):在要遷入的地方加入雙大括號。
屬性繫結 (Property Binding):在要繫結的屬性對象加上中括號。
當前端樣板狀態發生變化時,可以主動觸發後端類別的方法(也可以直接嵌入程式邏輯)。
事件繫結 (Event Binding):在要繫結的事件對象加上小括號。
接下來我們開始建置行事曆的明細項目,並且活用繫結的特性讓我們可以減少透過程式邏輯來控制介面。
開啟 src\app\employee\calendar\calendar.component.ts
,賦予 getDay
方法參數 addDMonth
預設值 0
,透過設定預設值的方式,讓我們在呼叫該方法時可以省略參數,也就是說當呼叫方法時若沒有帶入參數值則 TypeScript 自動帶入預設值。
後續會逐漸加入 TypeScript 的實用技巧,畢竟先學會 TypeScript,再比較 TypeScript 與 JavaScript 哪個比較好才有意義,這會比光看網路上別人的評論來決定還要準確,因為每個人的認知會都不同。
這便再次利用 Angular Flex-Layout 來幫我們切版,開啟 src\app\employee\calendar\calendar.component.html
外層加入 div
tag 來分割,預設是水平排列(fxLayout="row"
),但是考慮到手機尺寸,我們在加上 fxLayout.xs="column",讓手機尺寸可以改為垂直排列。 行事曆的區塊在水平排列時會占用 70% (
fxFlex=”70”),而在垂直排列時則會依內容大小填入,因為
fxLayout 預設模式是填滿(
stretch`),所以行事曆就會填滿整個寬度。
{% codeblock calendar.component.html lang:html %}
</md-card-content>
</md-card>
{% endcodeblock %}
用瀏覽器檢視可以看到桌機與手機的差異。
Angular 提供許多 通道(Pipe) 讓我們可以對 內嵌繫結 的資料再進一步的轉換,很顯然明細區塊的 selectedDay
所顯示的結果並不是我們想要的,因此這邊我們利用 DatePipe 來將日期格式化成 年/月/日
。
{% codeblock calendar.component.html lang:html %}
</md-card-content>
</md-card>
{% endcodeblock %}
Angular 目前提供的通道可以查詢 API 文件
如果功能不敷使用其實我們也可以自己撰寫 Pipe。
接下來我們在行事曆樣板上的日期 元件(md-grid-tile
) 加入點擊(click
)的事件,並透過事件繫結來繫結到元件類別的 selectdDay(item)
方法。
接著在 md-grid-tile
裡面再加上一個水藍色的 div
外框,並透過 *ngIf 指令來依照 item
的 isSelected
屬性決定是否顯示,接下來只要將選取到的日期所屬 item
的 isSelected
屬性改成 true
,其餘改成 false
就可以呈現選取效果。
編輯 calendar.component.html
。
{% codeblock calendar.component.html lang:html %}
</md-card-content>
</md-card>
{% endcodeblock %}
編輯 calendar.component.scss
。
{% codeblock calendar.component.scss lang:scss %}
.day {
border: solid 1px DimGray;
.text {
position: absolute;
top: 4px;
right: 8px;
}
.selected {
position: absolute;
top: 0px;
left: 0px;
box-sizing: border-box;
border: dashed 4px skyblue;
}
}
{% 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 = ‘’;
dayHeaders = [‘日’, ‘一’, ‘二’, ‘三’, ‘四’, ‘五’, ‘六’];
dayColors = [‘red’, ‘white’, ‘white’, ‘white’, ‘white’, ‘white’, ‘green’];
days = [];
lastMonth_colspan = 0;
selectedItem: any;
constructor() { }
ngOnInit() {
this.getToday();
}
getToday() {
this.selectedDay = new Date();
this.getDay();
}
getDay(addDMonth: number = 0) {
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 maxDay = new Date(year, month + 1, 0).getDate();
const newDay = this.selectedDay.getDate();
this.selectedDay = new Date(year, month, (newDay < maxDay) ? newDay : maxDay);
const dayNumber = this.selectedDay.getDate();
this.lastMonth_colspan = new Date(year, month, 1).getDay();
const _days = [];
for (let day = 1; day <= 31; day++) {
const time = new Date(year, month, day);
if (time.getMonth() > month) {
break;
}
const isSelected = time.getDate() === dayNumber;
const d: any = {
isSelected: isSelected,
datetime: time,
day: day,
week: time.getDay()
};
if (isSelected) {
this.selectedItem = d;
}
_days.push(d);
}
this.days = [..._days];
month++;
// month為13時表示隔年的1月。
if (month === 13) {
month = 1;
year++;
}
this.selectedMonth = `${year} 年 ${month} 月`;
}
selectdDay(item: any) {
if (this.selectedItem) {
this.selectedItem.isSelected = false;
}
item.isSelected = true;
this.selectedItem = item;
this.selectedDay = item.datetime;
}
}
{% endcodeblock %}
查看瀏覽器,選取效果已經出現。
接著我們在 calendar.component.ts
內建立一個讀取紀錄的方法-getNote()
,並透過亂數函式來產生一些紀錄。
{% 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 {
…
getDay(addDMonth: number = 0) {
let year = this.selectedDay.getFullYear();
…
this.selectedMonth = ${year} 年 ${month} 月
;
this.getNote();
}
selectdDay(item: any) {
…
}
getNote() {
if (this.days.length > 0) {
const d = Math.floor(Math.random() * 28);
this.days.forEach(item => {
const notes = [];
if (item.week !== 0 && item.week !== 6) {
if (item.week === 1) {
notes.push(‘8:00 每周會議’);
}
let b = Math.random() >= 0.5;
if (b) {
notes.push(‘XXX客戶拜訪’);
}
b = Math.random() >= 0.5;
if (b) {
notes.push(‘專案討論’);
}
if (item.day === d) {
notes.push(‘部門聚餐’);
}
}
item.notes = notes;
});
}
}
}
{% endcodeblock %}
編輯 calendar.component.html
。
{% codeblock calendar.component.html lang:html %}
{% endcodeblock %}
查看瀏覽器,選取日期時右邊已經會出現紀錄。
之前我們使用了 *ngFor
、*ngIf
指令,接下來我們練習另一個指令-[ngSwitch]
,使用上跟程式語言的 switch 語法雷同,配合 *ngSwitchCase
與 *ngSwitchDefault
與條件式比對,將符合的值所對應的樣板顯示出來,比較特別的是 ngSwitch 是用中括號([ ]
)包起來,而不是在前面加上星號(*
)。
首先我們修改 calendar.component.ts
內的 紀錄(notes
) 的資料結構,將字串陣列改成物件陣列,原本字串的內容移至物件內的 subject
屬性,並再增加 type
屬性當作分類用途。
{% 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 {
…
getNote() {
if (this.days.length > 0) {
const d = Math.floor(Math.random() * 28);
this.days.forEach(item => {
const notes = [];
if (item.week !== 0 && item.week !== 6) {
if (item.week === 1) {
notes.push({ type: 1, subject: ‘8:00 每周會議’ });
}
let b = Math.random() >= 0.5;
if (b) {
notes.push({ type: 2, subject: ‘XXX客戶拜訪’ });
}
b = Math.random() >= 0.5;
if (b) {
notes.push({ type: 3, subject: ‘專案討論’ });
}
if (item.day === d) {
notes.push({ type: 4, subject: ‘部門聚餐’ });
}
}
item.notes = notes;
});
}
}
}
{% endcodeblock %}
接著編輯 calendar.component.html
,透過 [ngSwitch]
指令來依紀錄分類顯示對應的圖示。
{% codeblock calendar.component.html lang:html %}
{% endcodeblock %}
ng-container
是一個比較特別的 tag,因為在執行時期它不會出現在樣板上,所以很適合拿來拆解過長的指令,例如我們需要在包含*ngFor
指令的樣板元素內再加上*ngIf
指令,以往方式可能將*ngFor
往上拉一層並透過div
tag 包覆,但是會造成每個元素都多包一個div
,這不只會增加運算成本,div
還有機會被其他 CSS 樣式影響而造成版面效果與預期不同,這時如改用ng-container
來替換,因為不會輸出成 tag 所以不會增加運算成本也不會受到 CSS 樣式影響。
檢視範例的內容可以發現最終只剩下md-icon
tag,綠色區塊是[ngSwitch]
產生的樣板,但是最後輸出時會被標示成註解。
編輯 calendar.component.scss
,增加 note 樣式。
{% codeblock calendar.component.scss lang:scss %}
.day {
border: solid 1px DimGray;
.text {
position: absolute;
top: 4px;
right: 8px;
}
.selected {
position: absolute;
top: 0px;
left: 0px;
box-sizing: border-box;
border: dashed 4px skyblue;
}
.note {
position: absolute;
bottom: 4px;
left: 4px;
}
}
{% endcodeblock %}
查看瀏覽器,日期下方多了圖示。
{% img /images/download.png 36 %}first-app_2017-09-13.zip