Template variable 可以讓你在 template 的任意一處使用被標記過的 HTML 元件的數據,例如響應使用者的輸入或微調應用程序的表單,簡單來說當你在畫面中有一個 <input>,除了透過 Form 獲得使用者在這個 <input> 所以輸入的數據之外,也可以透過將這個 <input> 設定為 template variable,這樣就可以讓在別的地方的 <button> 中的 event binding 獲得這個 <input> 的數據,詳細的內容讓我們繼續看下去吧。

要將 template 中的元件聲明為 template variable,需要在這個元件上加上哈希符號 #,舉個例子
<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>
雖然在 template 中 <button> 離 <input> 很遠,但因為<input> 透過 # 被標記為 template variable,所以遠處的 <button> 可以透過呼叫 phone.value 獲取這個 <input> 的值。
了解了 template variable 後,接著要來介紹 Angular 是如何根據你聲明變量的位置為 template variable 分配一個值:
<ng-template> 上聲明變量,則該變量引用一個 TemplateRef 的 instance,它代表著這個 template,這個之後會詳細介紹在大多數情況下,Angular 將 template varibale 的值設置為他出現的元素,比如上面的例子,phone 是指 <input>,而點擊了按鈕則會將 <input> 的內容傳遞給 component 中的 callPhone() method。
不過也可以透過在變量右邊指定一個名稱達到其他的效果,比如說可以使用 NgForm directive 來達到 Form 的效果,他通過引用 directive 的 exportAs name 來獲取對不同值得引用,來舉個例子吧
在 app.component.ts 中加入 property 與 method
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
submitMessage: string = ''; // (1)
onSubmit($event: any) { // (2)
this.submitMessage = $event.form.value.name;
}
}
<input> 的值在 app.component.html 中添加 <form>
<!-- app.component.html -->
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
<label for="name"
>Name <input class="form-control" name="name" ngModel required />
</label>
<button type="submit">Submit</button>
</form>
<div [hidden]="!itemForm.form.valid">
<p>{{ submitMessage }}</p>
</div>

如果沒有在 #itemForm 右邊使用 ngForm 則 #itemForm 的引用值將是 HTMLFormElement 的 <form> 元件,但這邊是使用了 ngForm 所以 #itemForm 是對 NgForm directive 的引用,他可以跟蹤表單中的每一個可控制元件的值和有效性,這與原生的 <form> 元素不同,NgForm directive 中有一個 property,他用於檢測整個表單的有效性,所以當 itemForm.form.valid 無效時,則整個 NgForm 會將 submit 按鈕 disable。
既然提到 variable 就不免俗的要提到 scope,至於什麼是 scope?簡單來說 scope 就是一個變數的生存範圍,一但超出了這個範圍就無法存取到這個變數 至於詳細的內容可以看我這一篇 [JS] You Don't Know JavaScript [Scope & Closures] - What is Scope? 文章中有詳細的介紹什麼是 scope。
而 template variable 則可以在 template 中的任何一個位置中調用到,但是 Structural directive (*ngIf, *ngFor, <ng-template> ...) 他會充當 template 的邊界,所以你會無法訪問到 Structural directive 內部的 template variable。
就如同 Javascript 的 scope 一樣,template 中內部的 template 可以訪問外部 template 的變量,但相反的話不行,舉個在同層級的例子
<input #ref1 type="text" [(ngModel)]="firstExample" />
<span *ngIf="true">Value: {{ ref1.value }}</span>
當更改了 <input> 的內容時會立即更改 <span> 中的內容,因為 Angular 會立即通過 template variable ref1 來更新內容,接著再舉一個由內部訪問到外部變量的例子
<input #ref1 type="text" [(ngModel)]="firstExample" />
<!-- New template -->
<ng-template [ngIf]="true">
<!-- Because the context is inherited, the value is available to the new template -->
<span>Value: {{ ref1.value }}</span>
</ng-template>
像上面提到的,<ng-template> 會創造一個新的 template 範圍,但是因為在這個新的 template 範圍中的 <span> 是從內部訪問外部的變量 ref1,所以是可以正常訪問到的,就跟 Javascript 一樣
const ref1 = 'input value';
function spanValue() {
console.log(ref1); // input value
}
雖然 ref1 與 function spanValue 是不同的 scope,但因為內部可以訪問外部變量,所以可以將 ref1 給 console 出來,接著來看父層如果訪問子層變量會發生什麼事
<ng-template [ngIf]="true">
<!-- The reference is defined within a template -->
<input #ref2 type="text" [(ngModel)]="secondExample" />
</ng-template>
<!-- ref2 accessed from outside that template doesn't work -->
<span>Value: {{ ref2?.value }}</span>
如果是上面例子的情況,<span> 會無法獲得 ref2 的內容,因為對於 ref2 來說他是存在於子層的變量,所以無法透過父層呼叫到,就跟 Javascript 一樣
function inputScope() {
let ref2 = 'in child scope';
}
console.log(ref2); // Uncaught ReferenceError: ref2 is not defined
外部無法呼叫到內部 scope 的變量,所以在使用 template variable 時要記得存取變量的規則, 外部 scope 無法存取到內部 scope 的變量。
本章中介紹了什麼是 template variable 與他的使用方法,簡單來說就是可以利用他獲得其他 element 的內容,不過因為他也是屬於變量所以也會有 scope 的問題,要記住外部 scope 是無法訪問到內部 scope 的變量的,而使用了 Structural directive 則會創造一個獨立的 template 讓整個 template 出現父子層的現象,就跟在 javescript 中使用 function 建立 function scope 一樣,所以要特別注意。
而本篇也是講解 template 的最後一篇,明天開始將會進入到 directive,這個觀念在前面多多少少都有提到一點,但沒關係之後會詳細地對他進行講解,那我們就明天見吧。