[Angular] Day12. Template variables

Template variable 可以让你在 template 的任意一处使用被标记过的 HTML 元件的数据,例如响应使用者的输入或微调应用程序的表单,简单来说当你在画面中有一个 <input>,除了透过 Form 获得使用者在这个 <input> 所以输入的数据之外,也可以透过将这个 <input> 设定为 template variable,这样就可以让在别的地方的 <button> 中的 event binding 获得这个 <input> 的数据,详细的内容让我们继续看下去吧。

https://ithelp.ithome.com.tw/upload/images/20210821/20124767lMmC7T1IVB.png


Syntax

要将 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> 的值。


How Angular assigns values to template variables

了解了 template variable 後,接着要来介绍 Angular 是如何根据你声明变量的位置为 template variable 分配一个值:

  • 如果在 component 的 selector 上声明变量,则是指这个 component 的 instance
  • 如果在标准 HTML element 上声明变量,则是指这个 HTML 的 element
  • 如果在 <ng-template> 上声明变量,则该变量引用一个 TemplateRef 的 instance,它代表着这个 template,这个之後会详细介绍
  • 如果在变量右边指定了一个名称,例如 `#var="ngModel",则该变量引用匹配 ngModel 名称 (exportAs name) 的元素上的 directive 或 component

Using NgForm with template variables

在大多数情况下,Angular 将 template varibale 的值设置为他出现的元素,比如上面的例子,phone 是指 <input>,而点击了按钮则会将 <input> 的内容传递给 component 中的 callPhone() method。

不过也可以透过在变量右边指定一个名称达到其他的效果,比如说可以使用 NgForm directive 来达到 Form 的效果,他通过引用 directive 的 exportAs name 来获取对不同值得引用,来举个例子吧

  1. 在 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;
      }
    }
    
    • (1): 新增 property 用於在画面中显示 <input> 的值
    • (2): 新增一个 method 用於当使用者点击按钮时触发
  2. 在 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>
    

img

如果没有在 #itemForm 右边使用 ngForm 则 #itemForm 的引用值将是 HTMLFormElement<form> 元件,但这边是使用了 ngForm 所以 #itemForm 是对 NgForm directive 的引用,他可以跟踪表单中的每一个可控制元件的值和有效性,这与原生的 <form> 元素不同,NgForm directive 中有一个 property,他用於检测整个表单的有效性,所以当 itemForm.form.valid 无效时,则整个 NgForm 会将 submit 按钮 disable。


Template variable scope

既然提到 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。

Accessing in a nested template

就如同 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,这个观念在前面多多少少都有提到一点,但没关系之後会详细地对他进行讲解,那我们就明天见吧。


Reference


<<:  D12 - 如何用 Apps Script 寄出客制化的表单并搜集分散在 Google Sheet 中的回应?(二)大幅度客制你的 Google Form

>>:  (Day12) 物件,浅拷贝/深拷贝

【第十九天 - PHP反序列化(1)】

Q1. 什麽是 php 反序列化? 为了让程序中的物件可以在保存到 persistent datab...

[Day 2] SRE - 你的服务死後不要让人担心嘛

graceful shutdown 在关闭服务前,在服务内部以做完该做的事情,使得服务得以善终。 在...

[Day30]用Canvas打造自己的游乐场-挑战心得

30天的挑战就这样结束了,现在的心情怎麽有些空虚呢?? 哈哈哈,为什麽会这样呢??我想跟题目有关吧,...

[Day01] Python 入门,总是可以多学到一点

写在前面 基本上分享会很随意, 主要就是纪录一些我觉得有趣或是 Kaggle 这系列想传达一些 py...

Day-17 同步、非同步与事件循环

JavsScripe是一套非同步的、单执行绪(single-threaded)语言,任务与任务之间必...