快訊!我的新書登上天瓏網路書店11月份暢銷榜第一名啦!看過的都說讚!歡迎大家前往訂購!
>>>> AI 職場超神助手:ChatGPT 與生成式 AI 一鍵搞定工作難題 <<<<

[功能介紹-2] 資料繫結的模版語法

Template Syntax

在上一篇的Angular架構中有提到,透過模版語法,template可以與component做許多的溝通。

那這一篇就會介紹Angular內建的模版語法參考。

對於tempalte.html來說,所有的html標籤都可以使用,除了<script>以外,這是為了維護模版的安全性,去除template被攻擊的風險,因此在template中所有的<script>會被忽略,並跳出警告。

接下來我們來介紹所有Angular的模板語法

{{…}}

以下為一個範例

<h3>
  {{title}}
  <img src="{{heroImageUrl}}" style="height:30px">
</h3>

大括號之間值通常是組件屬性的名稱。Angular使用相應組件屬性的字符串值替換該名稱。
在上面的例子中,Angular會取元件裡title和heroImageUrl的屬性,並會取代{{title}}及{{heroImageUrl}},因此在頁面上會顯示一個大的應用程序標題,然後一個英雄影像的位置。

在{{…}}之間,我們也可以使用模板表達式去轉換要顯示的值。
例如在刮弧中做運算:

<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>

或者也可以呼叫component的function getVal():

<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>

大多的運算符都可以用在表達式裡面,除了一些會影響到component的值的運算符,如=、+=、-=之類。

有時候{{…}}裡面要綁定的數值也可以是在template定義的(使用#符號),請見下面的範例

<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}

「註」:如果在元件裡已經有變數名稱叫做hero,而template裡又有一個hero,在template會優先使用template內定義的變數。
在表達式中不能引用任何除了undefined外的全域變數,如window或document,也不能用consolo.log,只能使用元件內的屬性或方法,或者template裡上下文內的成員

在使用{{…}}時,有下面四個原則要注意:

  • No visible side effects:不應該改變任何元件內的值,在rendering整個表式示時應該是穩定的
  • Quick execution:表達式的運算應該要很快,因為它會在許多狀況下被呼叫,因此若是裡面含有許多複雜運算時,請考慮用快取以增加效能。
  • Simplicity:雖然可以在{{…}}裡面寫很複雜的運算但是不建議,最多在裡面使用!符號,不然還是建議將運算放到元件內去計算,以利閱讀及開發
  • Idempotent: idempotent的意思是如果相同的操作再執行第二遍第三遍,結果還是跟第一遍的結果一樣 (也就是說不管執行幾次,結果都跟只有執行一次一樣)。

(event)=”statement”

(event)=”statement”是Template statements。事件綁定是利用這樣的方式去做的,下面是一個範例:

<button (click)="deleteHero()">Delete hero</button>

與{{…}}不同的是,在這樣的語法中,是可以去改變元件的值的,並且被改變的值會透過單向綁定{{…}}顯示在畫面上。因此,(event)=”statement”的statement是能夠有支援=運算符的,但是+=、-=和++、–是不被允許的。

語句上下文也可以引用模板自己的上下文的屬性。在以下範例中,將模版$event對象,模板輸入變量(let hero)和模板引用變量(#heroForm)傳遞給組件的事件處理方法。
下面是一個範例

<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>

[target]=”statement”

以下是一個範例:

<!-- Bind button disabled state to `isUnchanged` property -->
<button &#91;disabled&#93;="isUnchanged">Save</button>

這樣的語法,能夠讓isUnchanged為true時,顯示這樣的畫面

<button disabled>Save</button>

綁定方法整理

資料方向 語法 類型
單向綁定
從資料源到view
{{expression}}
[target]="expression"
bind-target="expression"
Interpolation
Property
Attribute
Class
Style
單向綁定
從view的目標到資料源
(target)="statement"
on-target="statement"
Event
雙向綁定
[(target)]="expression"
bindon-target="expression"
Two-way

HTML attribute與DOM property

HTML attribute和DOM property(物件屬性)的區別對於理解Angular綁定是很重要的。一旦使用插值(`{{…}}`),就不是使用HTML attribute,而是在設置DOM property。

一些HTML attribute可以1:1的對應到一個DOM property,例如:id
一些HTML attribute沒有相應的DOM property,例如:colspan(無法使用插值)。
一些DOM property沒有相應的HTML attribute,例如:textContent
許多DOM property似乎對應到HTML attribute…但不是以你想像的方式!

HTML attribute的value指定初始值; DOM property的value屬性是當前值

例如,當瀏覽器執行下面HTML,它創建一個對應的DOM節點,其value屬性初始化為“Bob”。

<input type="text" value="Bob">

當用戶在輸入框中輸入“Sally”時,DOM property的value變成“Sally”。
但是,HTML attribute的value保持不變,如下所示:

input.getAttribute('value');
//取的的值還是返回“Bob”

disabled屬性是另一個特殊的例子。按鈕的disabled 屬性是false默認的,所以按鈕被啟用。當你添加disabled 屬性時,它的存在會初始化按鈕的disabled 屬性,true所以按鈕被禁用。添加和刪除disabled屬性禁用並啟用按鈕。屬性的值是不相關的,這就是為什麼你不能用下面的語法來將按鈕設為enable。

<button disabled="false">Still Disabled</button>

因此,HTML attribute和DOM property是不一樣的,即使它們具有相同的名稱。

綁定目標整理

類型 目標 例子
屬性 元素屬性
組件屬性
指令屬性
<img &#91;src&#93;="heroImageUrl">
<app-hero-detail &#91;hero&#93;="currentHero"></app-hero-detail>
<div &#91;ngClass&#93;="{'special': isSpecial}"></div>
事件 元素事件
組件事件
指令事件
<button (click)="onSave()">Save</button>
<app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
<div (myClick)="clicked=$event" clickable>click me</div>
雙向 事件和財產
<input &#91;(ngModel)&#93;="name">
屬性 屬性(例外)
<button &#91;attr.aria-label&#93;="help">help</button>
class 屬性
<div &#91;class.special&#93;="isSpecial">Special</div>
樣式 style 屬性
<button &#91;style.color&#93;="isSpecial ? 'red' : 'green'">

Property binding or interpolation?

屬性綁定與插值常常能達到相同的功效,如下面的範例:

<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img &#91;src&#93;="heroImageUrl"> is the <i>property bound</i> image.</p>

<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span &#91;innerHTML&#93;="title"></span>" is the <i>property bound</i> title.</p>

一般而言,為了易讀性,會使用插值({{…}})。但當沒有要綁定的元素屬性時,必須使用屬性綁定。例如:

<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>

會得到這個錯誤
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property

這是因為插值只能設定properties而不能設定attributes。
這時可以改成

<table border=1>
  <!--  expression calculates colspan=2 -->
  <tr><td &#91;attr.colspan&#93;="1 + 1">One-Two</td></tr>

  <!-- ERROR: There is no `colspan` property to set!
    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
  -->

  <tr><td>Five</td><td>Six</td></tr>
</table>

則可以正常顯示如下圖

Built-in structural directives

常見的結構指令如下:

  • NgIf – 有條件地從DOM中添加或刪除一個元素,要注意,這和css的show、hide不一樣,當元素被dom移除時,是沒有辦法去操作DOM元素裡的物件的。
    <app-hero-detail *ngIf="isActive"></app-hero-detail>
  • NgSwitch – 一組在不同視圖之間切換的指令
    <div &#91;ngSwitch&#93;="currentHero.emotion">
      <app-happy-hero    *ngSwitchCase="'happy'"    &#91;hero&#93;="currentHero"></app-happy-hero>
      <app-sad-hero      *ngSwitchCase="'sad'"      &#91;hero&#93;="currentHero"></app-sad-hero>
      <app-confused-hero *ngSwitchCase="'confused'" &#91;hero&#93;="currentHero"></app-confused-hero>
      <app-unknown-hero  *ngSwitchDefault           &#91;hero&#93;="currentHero"></app-unknown-hero>
    </div>

  • NgForOf – 為列表中的每個項目重複一個模板
    <app-hero-detail *ngFor="let hero of heroes" &#91;hero&#93;="hero"></app-hero-detail>

Template reference variables ( #var )

在Angular也可以使用#開頭來將使用者在網頁上input輸入的值轉為一個變數,如下:

<input #phone placeholder="phone number">

<!-- lots of other elements -->

<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>

這個功能在做表單驗證時非常的方便

<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
  <div class="form-group">
    <label for="name">Name
      <input class="form-control" name="name" required &#91;(ngModel)&#93;="hero.name">
    </label>
  </div>
  <button type="submit" &#91;disabled&#93;="!heroForm.form.valid">Submit</button>
</form>
<div &#91;hidden&#93;="!heroForm.form.valid">
  {{submitMessage}}
</div>

允許外部元件讀取元件內的屬性

要讓元件內的屬性能夠給其他元件使用,或者讀取其他元件的屬性,可以在component.ts內宣告

@Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<hero>();

或者這樣也可以

@Component({
  inputs: ['hero'],
  outputs: ['deleteRequest'],
})

輸入屬性通常接收數據值。 輸出屬性會發送事件,如EventEmitter。
下面的圖顯示元件屬性的輸入和輸出的範例。

safe navigation operator ( ?. )

為了防止出現null reference exception,我們可以使用?.,當值為空值時,會直接傳回空白,可以避免產生不必要的exception
以下為範例

The current hero's name is {{currentHero?.name}}

參考資料

完整綁定範例資料請見此:線上範例


17年資歷女工程師,專精於動畫、影像辨識以及即時串流程式開發。經常組織活動,邀請優秀的女性分享她們的技術專長,並在眾多場合分享自己的技術知識,也活躍於非營利組織,辦理活動來支持特殊兒及其家庭。期待用技術改變世界。

如果你認同我或想支持我的努力,歡迎請我喝一杯咖啡!讓我更有動力分享知識!