Angular9 模板語法

2020-06-30 18:00 更新

這是一篇關(guān)于 Angular 模板語言的技術(shù)大全。 在本篇中解釋了模板語言的基本原理,并描述了你將在本教程的學(xué)習(xí)中遇到的大部分語法。

Angular 應(yīng)用管理著用戶之所見和所為,并通過 Component 類的實(shí)例(組件)和面向用戶的模板交互來實(shí)現(xiàn)這一點(diǎn)。

從使用模型-視圖-控制器 (MVC) 或模型-視圖-視圖模型 (MVVM) 的經(jīng)驗(yàn)中,很多開發(fā)人員都熟悉了組件和模板這兩個(gè)概念。 在 Angular 中,組件扮演著控制器或視圖模型的角色,模板則扮演視圖的角色。

這是一篇關(guān)于 Angular 模板語言的技術(shù)大全。 它解釋了模板語言的基本原理,并描述了你將在文檔中其它地方遇到的大部分語法。

模板中的 HTML

HTML 是 Angular 模板的語言。幾乎所有的 HTML 語法都是有效的模板語法。 但值得注意的例外是 <script>元素,它被禁用了,以阻止腳本注入攻擊的風(fēng)險(xiǎn)。(實(shí)際上,<script> 只是被忽略了。)

有些合法的 HTML 被用在模板中是沒有意義的。比如<html>、<body><base> 這幾個(gè)元素在這其中并沒有扮演有用的角色。剩下的所有元素基本上就都有其所用了。

可以通過組件和指令來擴(kuò)展模板中的 HTML 詞匯。它們看上去就是新元素和屬性。接下來將學(xué)習(xí)如何通過數(shù)據(jù)綁定來動(dòng)態(tài)獲取/設(shè)置 DOM(文檔對(duì)象模型)的值。

首先看看數(shù)據(jù)綁定的第一種形式 —— 插值,它展示了模板的 HTML 可以有多豐富。

插值與模板表達(dá)式

插值能讓你把計(jì)算后的字符串合并到 HTML 元素標(biāo)簽之間和屬性賦值語句內(nèi)的文本中。模板表達(dá)式則是用來供你求出這些字符串的。

插值 {{...}}

所謂 "插值" 是指將表達(dá)式嵌入到標(biāo)記文本中。 默認(rèn)情況下,插值會(huì)用雙花括號(hào) {{ }} 作為分隔符。

在下面的代碼片段中,{{ currentCustomer }} 就是插值的例子。

Path:"src/app/app.component.html"

<h3>Current customer: {{ currentCustomer }}</h3>

花括號(hào)之間的文本currentCustomer通常是組件屬性的名字。Angular 會(huì)把這個(gè)名字替換為響應(yīng)組件屬性的字符串值。

Path:"src/app/app.component.html"

<p>{{title}}</p>
<div><img src="{{itemImageUrl}}"></div>

在上面的示例中,Angular 計(jì)算 titleitemImageUrl 屬性并填充空白,首先顯示一些標(biāo)題文本,然后顯示圖像。

一般來說,括號(hào)間的素材是一個(gè)模板表達(dá)式,Angular 先對(duì)它求值,再把它轉(zhuǎn)換成字符串。 下列插值通過把括號(hào)中的兩個(gè)數(shù)字相加說明了這一點(diǎn):

Path:"src/app/app.component.html"

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

這個(gè)表達(dá)式可以調(diào)用宿主組件的方法,就像下面用的 getVal()

Path:"src/app/app.component.html"

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

Angular 對(duì)所有雙花括號(hào)中的表達(dá)式求值,把求值的結(jié)果轉(zhuǎn)換成字符串,并把它們跟相鄰的字符串字面量連接起來。最后,把這個(gè)組合出來的插值結(jié)果賦給元素或指令的屬性。

你看上去似乎正在將結(jié)果插入元素標(biāo)簽之間,并將其賦值給屬性。 但實(shí)際上,插值是一種特殊語法,Angular 會(huì)將其轉(zhuǎn)換為屬性綁定。

注:
- 如果你想用別的分隔符來代替 {{ }},也可以通過 Component 元數(shù)據(jù)中的 interpolation 選項(xiàng)來配置插值分隔符。

模板表達(dá)式

模板表達(dá)式會(huì)產(chǎn)生一個(gè)值,并出現(xiàn)在雙花括號(hào) {{ }} 中。 Angular 執(zhí)行這個(gè)表達(dá)式,并把它賦值給綁定目標(biāo)的屬性,這個(gè)綁定目標(biāo)可能是 HTML 元素、組件或指令。

{{1 + 1}} 中所包含的模板表達(dá)式是 1 + 1。 在屬性綁定中會(huì)再次看到模板表達(dá)式,它出現(xiàn)在 = 右側(cè)的引號(hào)中,就像這樣:[property]="expression"。

在語法上,模板表達(dá)式與 JavaScript 很像。很多 JavaScript 表達(dá)式都是合法的模板表達(dá)式,但也有一些例外。

你不能使用那些具有或可能引發(fā)副作用的 JavaScript 表達(dá)式,包括:

  • 賦值 (=, +=, -=, ...)。

  • new、typeofinstanceof 等運(yùn)算符。

  • 使用 ;, 串聯(lián)起來的表達(dá)式。

  • 自增和自減運(yùn)算符:++--

  • 一些 ES2015+ 版本的運(yùn)算符。

和 JavaScript 語法的其它顯著差異包括:

  • 不支持位運(yùn)算,比如 |&

  • 新的模板表達(dá)式運(yùn)算符,例如 |,?. 和 !。

表達(dá)式上下文

典型的表達(dá)式上下文就是這個(gè)組件實(shí)例,它是各種綁定值的來源。 在下面的代碼片段中,雙花括號(hào)中的 recommended 和引號(hào)中的 itemImageUrl2 所引用的都是 AppComponent 中的屬性。

Path:"src/app/app.component.html"

<h4>{{recommended}}</h4>
<img [src]="itemImageUrl2">

表達(dá)式也可以引用模板中的上下文屬性,例如模板輸入變量,

let customer,或模板引用變量 #customerInput

Path:"src/app/app.component.html (template input variable)"

<ul>
  <li *ngFor="let customer of customers">{{customer.name}}</li>
</ul>

Path:"src/app/app.component.html (template reference variable)"

<label>Type something:
  <input #customerInput>{{customerInput.value}}
</label>

表達(dá)式中的上下文變量是由模板變量、指令的上下文變量(如果有)和組件的成員疊加而成的。 如果你要引用的變量名存在于一個(gè)以上的命名空間中,那么,模板變量是最優(yōu)先的,其次是指令的上下文變量,最后是組件的成員。

上一個(gè)例子中就體現(xiàn)了這種命名沖突。組件具有一個(gè)名叫 customer 的屬性,而 *ngFor 聲明了一個(gè)也叫 customer 的模板變量。

注:
- 在 {{customer.name}} 表達(dá)式中的 customer 實(shí)際引用的是模板變量,而不是組件的屬性。

  • 模板表達(dá)式不能引用全局命名空間中的任何東西,比如 windowdocument。它們也不能調(diào)用 console.logMath.max。 它們只能引用表達(dá)式上下文中的成員。

表達(dá)式使用指南

當(dāng)使用模板表達(dá)式時(shí),請(qǐng)遵循下列要素:

  1. 非常簡(jiǎn)單

雖然也可以寫復(fù)雜的模板表達(dá)式,不過最好避免那樣做。

屬性名或方法調(diào)用應(yīng)該是常態(tài),但偶然使用邏輯取反 ! 也是可以的。 其它情況下,應(yīng)該把應(yīng)用程序和業(yè)務(wù)邏輯限制在組件中,這樣它才能更容易開發(fā)和測(cè)試。

  1. 執(zhí)行迅速

Angular 會(huì)在每個(gè)變更檢測(cè)周期后執(zhí)行模板表達(dá)式。 變更檢測(cè)周期會(huì)被多種異步活動(dòng)觸發(fā),比如 Promise 解析、HTTP 結(jié)果、定時(shí)器時(shí)間、按鍵或鼠標(biāo)移動(dòng)。

表達(dá)式應(yīng)該快速結(jié)束,否則用戶就會(huì)感到拖沓,特別是在較慢的設(shè)備上。 當(dāng)計(jì)算代價(jià)較高時(shí),應(yīng)該考慮緩存那些從其它值計(jì)算得出的值。

  1. 沒有可見的副作用

模板表達(dá)式除了目標(biāo)屬性的值以外,不應(yīng)該改變應(yīng)用的任何狀態(tài)。

這條規(guī)則是 Angular “單向數(shù)據(jù)流”策略的基礎(chǔ)。 永遠(yuǎn)不用擔(dān)心讀取組件值可能改變另外的顯示值。 在一次單獨(dú)的渲染過程中,視圖應(yīng)該總是穩(wěn)定的。

冪等的表達(dá)式是最理想的,因?yàn)樗鼪]有副作用,并且可以提高 Angular 的變更檢測(cè)性能。 用 Angular 術(shù)語來說,冪等表達(dá)式總會(huì)返回完全相同的東西,除非其依賴值之一發(fā)生了變化。

在單獨(dú)的一次事件循環(huán)中,被依賴的值不應(yīng)該改變。 如果冪等的表達(dá)式返回一個(gè)字符串或數(shù)字,連續(xù)調(diào)用它兩次,也應(yīng)該返回相同的字符串或數(shù)字。 如果冪等的表達(dá)式返回一個(gè)對(duì)象(包括 Date 或 Array),連續(xù)調(diào)用它兩次,也應(yīng)該返回同一個(gè)對(duì)象的引用。

注:
- 對(duì)于 *ngFor,這種行為有一個(gè)例外。*ngFor 具有 trackBy 功能,在迭代對(duì)象時(shí)它可以處理對(duì)象的相等性。詳情參見 帶 trackBy*ngFor。

模板語句

模板語句用來響應(yīng)由綁定目標(biāo)(如 HTML 元素、組件或指令)觸發(fā)的事件。 模板語句將在事件綁定一節(jié)看到,它出現(xiàn)在 = 號(hào)右側(cè)的引號(hào)中,就像這樣:(event)="statement"。

Path:"src/app/app.component.html"

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

模板語句有副作用。 這是事件處理的關(guān)鍵。因?yàn)槟阋鶕?jù)用戶的輸入更新應(yīng)用狀態(tài)。

響應(yīng)事件是 Angular 中“單向數(shù)據(jù)流”的另一面。 在一次事件循環(huán)中,可以隨意改變?nèi)魏蔚胤降娜魏螙|西。

和模板表達(dá)式一樣,模板語句使用的語言也像 JavaScript。 模板語句解析器和模板表達(dá)式解析器有所不同,特別之處在于它支持基本賦值 (=) 和表達(dá)式鏈 (;)。

然而,某些 JavaScript 語法和模板表達(dá)式語法仍然是不允許的:

  • new 運(yùn)算符

自增和自減運(yùn)算符:++--

操作并賦值,例如 +=-=

位運(yùn)算符,例如 |&

管道運(yùn)算符

語句上下文

和表達(dá)式中一樣,語句只能引用語句上下文中 —— 通常是正在綁定事件的那個(gè)組件實(shí)例。

典型的語句上下文就是當(dāng)前組件的實(shí)例。 (click)="deleteHero()" 中的 deleteHero 就是這個(gè)數(shù)據(jù)綁定組件上的一個(gè)方法。

Path:"src/app/app.component.html"

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

語句上下文可以引用模板自身上下文中的屬性。 在下面的例子中,就把模板的 $event 對(duì)象、模板輸入變量 (let hero)和模板引用變量 (#heroForm)傳給了組件中的一個(gè)事件處理器方法。

Path:"src/app/app.component.html"

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

模板上下文中的變量名的優(yōu)先級(jí)高于組件上下文中的變量名。在上面的 deleteHero(hero) 中,hero 是一個(gè)模板輸入變量,而不是組件中的 hero 屬性。

語句指南

模板語句不能引用全局命名空間的任何東西。比如不能引用 windowdocument,也不能調(diào)用 console.logMath.max。

和表達(dá)式一樣,避免寫復(fù)雜的模板語句。 常規(guī)是函數(shù)調(diào)用或者屬性賦值。

綁定語法:概覽

數(shù)據(jù)綁定是一種機(jī)制,用來協(xié)調(diào)用戶可見的內(nèi)容,特別是應(yīng)用數(shù)據(jù)的值。 雖然也可以手動(dòng)從 HTML 中推送或拉取這些值,但是如果將這些任務(wù)轉(zhuǎn)交給綁定框架,應(yīng)用就會(huì)更易于編寫、閱讀和維護(hù)。 你只需聲明數(shù)據(jù)源和目標(biāo) HTML 元素之間的綁定關(guān)系就可以了,框架會(huì)完成其余的工作。

Angular 提供了多種數(shù)據(jù)綁定方式。綁定類型可以分為三類,按數(shù)據(jù)流的方向分為:

  1. 單向:從數(shù)據(jù)源到視圖

  • 綁定類型:插值、屬性、Attribute、CSS類、樣式。

  • 語法:

    {{expression}}
    [target]="expression"
    bind-target="expression"

  1. 單向:從視圖到數(shù)據(jù)源

  • 綁定類型:事件

  • 語法:

    (target)="statement"
    on-target="statement"

  • 雙向:視圖到數(shù)據(jù)源到視圖

  • 綁定類型:雙向

  • 語法:

    [(target)]="expression"
    bindon-target="expression"

除插值以外的其它綁定類型在等號(hào)的左側(cè)都有一個(gè)“目標(biāo)名稱”,由綁定符 []() 包起來, 或者帶有前綴:bind-,on-bindon-。

綁定的“目標(biāo)”是綁定符內(nèi)部的屬性或事件:[]、()[()]

在綁定時(shí)可以使用來源指令的每個(gè)公共成員。 你無需進(jìn)行任何特殊操作即可在模板表達(dá)式或語句內(nèi)訪問指令的成員。

數(shù)據(jù)綁定與 HTML

在正常的 HTML 開發(fā)過程中,你使用 HTML 元素來創(chuàng)建視覺結(jié)構(gòu), 通過把字符串常量設(shè)置到元素的 attribute 來修改那些元素。

<div class="special">Plain old HTML</div>


<img src="images/item.png">
<button disabled>Save</button>

使用數(shù)據(jù)綁定,你可以控制按鈕狀態(tài)等各個(gè)方面:

Path:"src/app/app.component.html"

<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>

注:
- 這里綁定到的是按鈕的 DOM 元素的 disabled 這個(gè) Property,而不是 Attribute。

  • 這是數(shù)據(jù)綁定的通用規(guī)則。數(shù)據(jù)綁定使用 DOM 元素、組件和指令的 Property,而不是 HTML 的 Attribute。

HTML attribute 與 DOM property 的對(duì)比

理解 HTML 屬性和 DOM 屬性之間的區(qū)別,是了解 Angular 綁定如何工作的關(guān)鍵。Attribute 是由 HTML 定義的。Property 是從 DOM(文檔對(duì)象模型)節(jié)點(diǎn)訪問的。

  • 一些 HTML Attribute 可以 1:1 映射到 Property;例如,“ id”。

  • 某些 HTML Attribute 沒有相應(yīng)的 Property。例如,aria-*。

  • 某些 DOM Property 沒有相應(yīng)的 Attribute。例如,textContent。

重要的是要記住,HTML Attribute 和 DOM Property 是不同的,就算它們具有相同的名稱也是如此。 在 Angular 中,HTML Attribute 的唯一作用是初始化元素和指令的狀態(tài)。

模板綁定使用的是 Property 和事件,而不是 Attribute。

編寫數(shù)據(jù)綁定時(shí),你只是在和目標(biāo)對(duì)象的 DOM Property 和事件打交道。

注:
- 該通用規(guī)則可以幫助你建立 HTML Attribute 和 DOM Property 的思維模型: 屬性負(fù)責(zé)初始化 DOM 屬性,然后完工。Property 值可以改變;Attribute 值則不能。

  • 此規(guī)則有一個(gè)例外。 可以通過 setAttribute() 來更改 Attribute,接著它會(huì)重新初始化相應(yīng)的 DOM 屬性。

示例 1:<input>

當(dāng)瀏覽器渲染 <input type="text" value="Sarah"> 時(shí),它會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的 DOM 節(jié)點(diǎn),其 value Property 已初始化為 “Sarah”。

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

當(dāng)用戶在 <input> 中輸入 Sally 時(shí),DOM 元素的 value Property 將變?yōu)?Sally。 但是,如果使用 input.getAttribute('value') 查看 HTML 的 Attribute value,則可以看到該 attribute 保持不變 —— 它返回了 Sarah。

HTML 的 value 這個(gè) attribute 指定了初始值;DOM 的 value 這個(gè) property 是當(dāng)前值。

示例 2:禁用按鈕

disabled Attribute 是另一個(gè)例子。按鈕的 disabled Property 默認(rèn)為 false,因此按鈕是啟用的。

當(dāng)你添加 disabled Attribute 時(shí),僅僅它的出現(xiàn)就將按鈕的 disabled Property 初始化成了 true,因此該按鈕就被禁用了。

<button disabled>Test Button</button>

添加和刪除 disabled , Attribute 會(huì)禁用和啟用該按鈕。 但是,Attribute 的值無關(guān)緊要,這就是為什么你不能通過編寫 <button disabled="false">仍被禁用</button> 來啟用此按鈕的原因。

要控制按鈕的狀態(tài),請(qǐng)?jiān)O(shè)置 disabled Property。

雖然技術(shù)上說你可以設(shè)置 [attr.disabled] 屬性綁定,但是它們的值是不同的,Property 綁定要求一個(gè)布爾值,而其相應(yīng)的 Attribute 綁定則取決于該值是否為 null。例子如下:

&
lt;input [disabled]="condition ? true : false"&
lt;input [attr.disabled]="condition ? 'disabled' : null"&

通常,要使用 Property 綁定而不是 Attribute 綁定,因?yàn)樗庇^(是一個(gè)布爾值),語法更短,并且性能更高。

綁定類型與綁定目標(biāo)

數(shù)據(jù)綁定的目標(biāo)是 DOM 中的對(duì)象。 根據(jù)綁定類型,該目標(biāo)可以是 Property 名(元素、組件或指令的)、事件名(元素、組件或指令的),有時(shí)是 Attribute 名。下表中總結(jié)了不同綁定類型的目標(biāo)。

  1. 綁定類型:屬性。

  • 目標(biāo):元素的 property 、組件的 property 、指令的 property 。

  • 示例:

    <img [src]="heroImageUrl">
    <app-hero-detail [hero]="currentHero"></app-hero-detail>
    <div [ngClass]="{'special': isSpecial}"></div>

  1. 綁定類型:事件。

  • 目標(biāo):元素的事件、組件的事件、指令的事件。

  • 示例:

    <button (click)="onSave()">Save</button>
    <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
    <div (myClick)="clicked=$event" clickable>click me</div>

  1. 綁定類型:雙向。

  • 目標(biāo):事件與 property。

  • 示例:

    <input [(ngModel)]="name">

  1. 綁定類型:Attribute 。

  • 目標(biāo):attribute(例外情況)。

  • 示例:

    <button [attr.aria-label]="help">help</button>

  1. 綁定類型:CSS 類。

  • 目標(biāo):class property 。

  • 示例:

    <div [class.special]="isSpecial">Special</div>

  1. 綁定類型:樣式。

  • 目標(biāo):style property 。

  • 示例:

    <button [style.color]="isSpecial ? 'red' : 'green'">

Property 綁定 [property]

使用 Property 綁定到目標(biāo)元素或指令 @Input() 裝飾器的 set 型屬性。

單向輸入

Property 綁定的值在一個(gè)方向上流動(dòng),從組件的 Property 變?yōu)槟繕?biāo)元素的 Property。

你不能使用屬性綁定從目標(biāo)元素讀取或拉取值。同樣的,你也不能使用屬性綁定在目標(biāo)元素上調(diào)用方法。如果元素要引發(fā)事件,則可以使用事件綁定來監(jiān)聽它們。

示例:

最常見的 Property 綁定將元素的 Property 設(shè)置為組件的 Property 值。例子之一是將 img 元素的 src Property 綁定到組件的 itemImageUrl Property

Path:"src/app/app.component.html"

<img [src]="itemImageUrl">

這是綁定到 colSpan Property 的示例。請(qǐng)注意,它不是 colspan,后者是 Attribute,用小寫的 s 拼寫。

Path:"src/app/app.component.html"

<!-- Notice the colSpan property is camel case -->
<tr><td [colSpan]="2">Span 2 columns</td></tr>

另一個(gè)例子是當(dāng)組件說它 isUnchanged(未改變)時(shí)禁用按鈕:

Path:"src/app/app.component.html"

<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Disabled Button</button>

另一個(gè)例子是設(shè)置指令的屬性:

Path:"src/app/app.component.html"

<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p

另一種方法是設(shè)置自定義組件的模型屬性 —— 這是一種父級(jí)和子級(jí)組件進(jìn)行通信的好辦法:

Path:"src/app/app.component.html"

<app-item-detail [childItem]="parentItem"></app-item-detail>

綁定目標(biāo)

包裹在方括號(hào)中的元素屬性名標(biāo)記著目標(biāo)屬性。下列代碼中的目標(biāo)屬性是 image 元素的 src 屬性。

Path:"src/app/app.component.html"

<img [src]="itemImageUrl">

還有一種使用 bind- 前綴的替代方案:

Path:"src/app/app.component.html"

<img bind-src="itemImageUrl">

在大多數(shù)情況下,目標(biāo)名都是 Property 名,雖然它看起來像 Attribute 名。因此,在這個(gè)例子中,src<img> 元素屬性的名稱。

元素屬性可能是最常見的綁定目標(biāo),但 Angular 會(huì)先去看這個(gè)名字是否是某個(gè)已知指令的屬性名,就像下面的例子中一樣:

Path:"src/app/app.component.html"

<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p>

從技術(shù)上講,Angular 將這個(gè)名稱與指令的 @Input() 進(jìn)行匹配,它來自指令的 inputs 數(shù)組中列出的 Property 名稱之一或是用 @Input() 裝飾的屬性。這些輸入都映射到指令自身的屬性。

如果名字沒有匹配上已知指令或元素的屬性,Angular 就會(huì)報(bào)告“未知指令”的錯(cuò)誤。

注:
- 盡管目標(biāo)名稱通常是 Property 的名稱,但是在 Angular 中,有幾個(gè)常見屬性會(huì)自動(dòng)將 Attribute 映射為 Property。這些包括 class / className,nnerHtml / innerHTMLtabindex / abIndex。

消除副作用

模板表達(dá)的計(jì)算應(yīng)該沒有明顯的副作用。表達(dá)式語言本身或你編寫模板表達(dá)式的方式在一定程度上有所幫助。你不能為屬性綁定表達(dá)式中的任何內(nèi)容賦值,也不能使用遞增和遞減運(yùn)算符。

例如,假設(shè)你有一個(gè)表達(dá)式,該表達(dá)式調(diào)用了具有副作用的屬性或方法。該表達(dá)式可以調(diào)用類似 getFoo() 的函數(shù),只有你知道 getFoo() 做了什么。如果 getFoo() 更改了某些內(nèi)容,而你恰巧綁定到該內(nèi)容,則 Angular 可能會(huì)也可能不會(huì)顯示更改后的值。Angular 可能會(huì)檢測(cè)到更改并拋出警告錯(cuò)誤。最佳實(shí)踐是堅(jiān)持使用屬性和返回值并避免副作用的方法。

返回正確的類型

模板表達(dá)式的計(jì)算結(jié)果應(yīng)該是目標(biāo)屬性所需要的值類型。如果 target 屬性需要一個(gè)字符串,則返回一個(gè)字符串;如果需要一個(gè)數(shù)字,則返回一個(gè)數(shù)字;如果需要一個(gè)對(duì)象,則返回一個(gè)對(duì)象,依此類推。

在下面的例子中,temDetailComponentchildItem 屬性需要一個(gè)字符串,而這正是你要發(fā)送給屬性綁定的內(nèi)容:

Path:"src/app/app.component.html"

<app-item-detail [childItem]="parentItem"></app-item-detail>

你可以查看 ItemDetailComponent 來確認(rèn)這一點(diǎn),它的 @Input 類型設(shè)為了字符串:

Path:"src/app/item-detail/item-detail.component.ts (setting the @Input() type)"

@Input() childItem: string;

如你所見,AppComponent 中的 parentItem 是一個(gè)字符串,而 ItemDetailComponent 需要的就是字符串:

Path:"src/app/app.component.ts"

parentItem = 'lamp';

傳入對(duì)象

前面的簡(jiǎn)單示例演示了傳入字符串的情況。要傳遞對(duì)象,其語法和思想是相同的。

在這種情況下,ItemListComponent 嵌套在 AppComponent 中,并且 items 屬性需要一個(gè)對(duì)象數(shù)組。

Path:"src/app/app.component.html"

<app-item-list [items]="currentItems"></app-item-list>

items 屬性是在 ItemListComponent 中用 Item 類型聲明的,并帶有 @Input() 裝飾器:

Path:"src/app/item-list.component.ts"

@Input() items: Item[];

在此示例應(yīng)用程序中,Item是具有兩個(gè)屬性的對(duì)象。一個(gè) id 和一個(gè) name。

Path:"src/app/item.ts"

export interface Item {
  id: number;
  name: string;
}

當(dāng)另一個(gè)文件 "mock-items.ts" 中存在一個(gè)條目列表時(shí),你可以在 "app.component.ts" 中指定另一個(gè)條目,以便渲染新條目:

Path:"src/app.component.ts"

currentItems = [{
  id: 21,
  name: 'phone'
}];

在這個(gè)例子中,你只需要確保你所提供的對(duì)象數(shù)組的類型,也就是這個(gè) Item 的類型是嵌套組件 `ItemListComponent 所需要的類型。

在此示例中,AppComponen 指定了另一個(gè) item 對(duì)象( urrentItems )并將其傳給嵌套的 ItemListComponent。ItemListComponent 之所以能夠使用 currentItems 是因?yàn)樗c "item.ts" 中定義的 Item 對(duì)象的類型相匹配。在 "item.ts" 文件中,ItemListComponent 獲得了其對(duì) item 的定義。

方括號(hào)

方括號(hào) [] 告訴 Angular 計(jì)算該模板表達(dá)式。如果省略括號(hào),Angular 會(huì)將字符串視為常量,并使用該字符串初始化目標(biāo)屬性 :

Path:"src/app.component.html"

<app-item-detail childItem="parentItem"></app-item-detail>

省略方括號(hào)將渲染字符串 parentItem,而不是 parentItem 的值。

一次性字符串初始化

當(dāng)滿足下列條件時(shí),應(yīng)該省略括號(hào):

  • 目標(biāo)屬性接受字符串值。

  • 字符串是一個(gè)固定值,你可以直接將其放入模板中。

  • 這個(gè)初始值永不改變。

你通常會(huì)以這種方式在標(biāo)準(zhǔn) HTML 中初始化屬性,并且它對(duì)指令和組件的屬性初始化同樣有效。 下面的示例將 StringInitComponent 中的 prefix 屬性初始化為固定字符串,而不是模板表達(dá)式。Angular 設(shè)置它,然后就不管它了。

Path:"src/app/app.component.html"

<app-string-init prefix="This is a one-time initialized string."></app-string-init>

另一方面,[item] 綁定仍然是與組件的 currentItems 屬性的實(shí)時(shí)綁定。

屬性綁定與插值

你通常得在插值和屬性綁定之間做出選擇。 下列這幾對(duì)綁定做的事情完全相同:

Path:"src/app/app.component.html"

<p><img src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="itemImageUrl"> is the <i>property bound</i> image.</p>


<p><span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="propertyTitle"></span>" is the <i>property bound</i> title.</p>

在許多情況下,插值是屬性綁定的便捷替代法。當(dāng)要把數(shù)據(jù)值渲染為字符串時(shí),雖然可讀性方面傾向于插值,但沒有技術(shù)上的理由偏愛一種形式。但是,將元素屬性設(shè)置為非字符串的數(shù)據(jù)值時(shí),必須使用屬性綁定。

內(nèi)容安全

假設(shè)如下惡意內(nèi)容:

Path:"src/app/app.component.ts"

evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';

在組件模板中,內(nèi)容可以與插值一起使用:

Path:"src/app/app.component.html"

<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>

幸運(yùn)的是,Angular 數(shù)據(jù)綁定對(duì)于危險(xiǎn)的 HTML 高度戒備。在上述情況下,HTML 將按原樣顯示,而 Javascript 不執(zhí)行。Angular 不允許帶有 script 標(biāo)簽的 HTML 泄漏到瀏覽器中,無論是插值還是屬性綁定。

不過,在下列示例中,Angular 會(huì)在顯示值之前先對(duì)它們進(jìn)行無害化處理。

Path:"src/app/app.component.html"

<!--
 Angular generates a warning for the following line as it sanitizes them
 WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
-->
 <p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>

插值處理 <script> 標(biāo)記與屬性綁定的方式不同,但是這兩種方法都可以使內(nèi)容無害。以下是 evilTitle 示例的瀏覽器輸出。

"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
"Template Syntax" is the property bound evil title.

attribute、class 和 style 綁定

模板語法為那些不太適合使用屬性綁定的場(chǎng)景提供了專門的單向數(shù)據(jù)綁定形式。

要在運(yùn)行中的應(yīng)用查看 Attribute 綁定、類綁定和樣式綁定,請(qǐng)參見 現(xiàn)場(chǎng)演練 / 下載范例 特別是對(duì)于本節(jié)。

attribute 綁定

可以直接使用 Attribute 綁定設(shè)置 Attribute 的值。一般來說,綁定時(shí)設(shè)置的是目標(biāo)的 Property,而 Attribute 綁定是唯一的例外,它創(chuàng)建和設(shè)置的是 Attribute

通常,使用 Property 綁定設(shè)置元素的 Property 優(yōu)于使用字符串設(shè)置 Attribute。但是,有時(shí)沒有要綁定的元素的 Property,所以其解決方案就是 Attribute 綁定。

考慮 ARIASVG。它們都純粹是 Attribute,不對(duì)應(yīng)于元素的 Property,也不能設(shè)置元素的 Property。 在這些情況下,就沒有要綁定到的目標(biāo) Property。

Attribute 綁定的語法類似于 Property 綁定,但其括號(hào)之間不是元素的 Property,而是由前綴 attr、點(diǎn)( . )和 Attribute 名稱組成。然后,你就可以使用能解析為字符串的表達(dá)式來設(shè)置該 Attribute 的值,或者當(dāng)表達(dá)式解析為 null 時(shí)刪除該 Attribute

attribute 綁定的主要用例之一是設(shè)置 ARIA attribute(譯注:ARIA 指無障礙功能,用于給殘障人士訪問互聯(lián)網(wǎng)提供便利), 就像這個(gè)例子中一樣:

Path:"src/app/app.component.html"

<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>

colspancolSpan
注意 colspan AttributecolSpan Property 之間的區(qū)別。

如果你這樣寫:

&
lt;tr&<td colspan="{{1 + 1}}"&Three-Four</td&</tr&

你會(huì)收到如下錯(cuò)誤:

&
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property

如錯(cuò)誤消息所示,<td& 元素沒有 colspan 這個(gè) Property。這是正確的,因?yàn)?colspan 是一個(gè) Attribute,而 colSpan (colSpan 中的 S 是大寫)則是相應(yīng)的 Property。插值和 Property 綁定只能設(shè)置 Property,不能設(shè)置 Attribute。

相反,你可以使用 Property 綁定并將其改寫為:

&Path:"src/app/app.component.html"

lt;!-- Notice the colSpan property is camel case --&
lt;tr&<td [colSpan]="1 + 1"&Three-Four</td&</tr&

類綁定

下面是在普通 HTML 中不用綁定來設(shè)置 class Attribute 的方法:

<!-- standard class attribute setting -->


<div class="foo bar">Some text</div>

你還可以使用類綁定來為一個(gè)元素添加和移除 CSS 類。

要?jiǎng)?chuàng)建單個(gè)類的綁定,請(qǐng)使用 class 前綴,緊跟一個(gè)點(diǎn)(.),再跟上 CSS 類名,比如 [class.foo]="hasFoo"。 當(dāng)綁定表達(dá)式為真值的時(shí)候,Angular 就會(huì)加上這個(gè)類,為假值則會(huì)移除,但 undefined 是假值中的例外,參見樣式委派 部分。

要想創(chuàng)建多個(gè)類的綁定,請(qǐng)使用通用的 [class] 形式來綁定類,而不要帶點(diǎn),比如 [class]="classExpr"。 該表達(dá)式可以是空格分隔的類名字符串,或者用一個(gè)以類名為鍵、真假值表達(dá)式為值的對(duì)象。 當(dāng)使用對(duì)象格式時(shí),Angular 只會(huì)加上那些相關(guān)的值為真的類名。

一定要注意,在對(duì)象型表達(dá)式中(如 objectArray、Map、Set 等),當(dāng)這個(gè)類列表改變時(shí),對(duì)象的引用也必須修改。僅僅修改其屬性而不修改對(duì)象引用是無法生效的。

如果有多處綁定到了同一個(gè)類名,出現(xiàn)的沖突將根據(jù)樣式的優(yōu)先級(jí)規(guī)則進(jìn)行解決。

綁定類型 語法 輸入類型 輸入值示例
單個(gè)類綁定 [class.foo]="hasFoo" boolean OR undefined OR null true, false
多個(gè)類綁定 [class]="classExpr" string OR {[key: string]: boolean / undefined / null} OR Array<string> | "my-class-1 my-class-2 my-class-3" OR {foo: true, bar: false} OR ['foo', 'bar']`

注:
- 多個(gè)類綁定的輸入類型按順序?qū)?yīng)一個(gè)示例,而不是一個(gè)類型對(duì)應(yīng)多個(gè)示例。

盡管此技術(shù)適用于切換單個(gè)類名,但在需要同時(shí)管理多個(gè)類名時(shí)請(qǐng)考慮使用 NgClass 指令。

樣式綁定

下面演示了如何不通過綁定在普通 HTML 中設(shè)置 style 屬性:

<!-- standard style attribute setting -->
<div style="color: blue">Some text</div>

你還可以通過樣式綁定來動(dòng)態(tài)設(shè)置樣式。

要想創(chuàng)建單個(gè)樣式的綁定,請(qǐng)以 style 前綴開頭,緊跟一個(gè)點(diǎn)(.),再跟著 CSS 樣式的屬性名,比如 [style.width]="width"。 該屬性將會(huì)被設(shè)置為綁定表達(dá)式的值,該值通常為字符串。 不過你還可以添加一個(gè)單位表達(dá)式,比如 em%,這時(shí)候該值就要是一個(gè) number 類型。

注:
- 樣式屬性命名方法可以用中線命名法,像上面的一樣 也可以用駝峰式命名法,如 fontSize。

如果要切換多個(gè)樣式,你可以直接綁定到 [style] 屬性而不用點(diǎn)(比如,[style]="styleExpr")。賦給 [style] 的綁定表達(dá)式通常是一系列樣式組成的字符串,比如 "width: 100px; height: 100px;"。

你也可以把該表達(dá)式格式化成一個(gè)以樣式名為鍵、以樣式值為值的對(duì)象,比如 {width: '100px', height: '100px'}。一定要注意,對(duì)于任何對(duì)象型的表達(dá)式( 如 object,ArrayMap,Set 等),當(dāng)這個(gè)樣式列表改變時(shí),對(duì)象的引用也必須修改。僅僅修改其屬性而不修改對(duì)象引用是無法生效的。。

如果有多處綁定了同一個(gè)樣式屬性,則會(huì)使用樣式的優(yōu)先級(jí)規(guī)則來解決沖突。

綁定類型 語法 輸入類型 輸入值示例
單一樣式綁定 [style.width]="width" string OR undefined OR null "100px"
帶單位的單一樣式綁定 [style.width.px]="width" number OR undefined OR null 100
多個(gè)樣式綁定 [style]="styleExpr" string OR {[key: string]: string / undefined / null} OR Array<string>` ['width', '100px']

NgStyle 指令可以作為 [style] 綁定的替代指令。但是,應(yīng)該把上面這種 [style] 樣式綁定語法作為首選,因?yàn)殡S著 Angular 中樣式綁定的改進(jìn),NgStyle 將不再提供重要的價(jià)值,并最終在未來的某個(gè)版本中刪除。

樣式的優(yōu)先級(jí)規(guī)則

一個(gè) HTML 元素可以把它的 CSS 類列表和樣式值綁定到多個(gè)來源(例如,來自多個(gè)指令的宿主 host 綁定)。

當(dāng)對(duì)同一個(gè)類名或樣式屬性存在多個(gè)綁定時(shí),Angular 會(huì)使用一組優(yōu)先級(jí)規(guī)則來解決沖突,并確定最終哪些類或樣式會(huì)應(yīng)用到該元素中。

樣式的優(yōu)先級(jí)規(guī)則(從高到低)

  1. 模板綁定

  1. 屬性綁定(例如 <div [class.foo]="hasFoo"& 或 <div [style.color]="color"&)

1. Map 綁定(例如,<div [class]="classExpr"& 或 <div [style]="styleExpr"& )

2. 靜態(tài)值(例如 <div class="foo"& 或 <div style="color: blue"& )

  1. 指令宿主綁定

  1. 屬性綁定(例如,host: {'[class.foo]': 'hasFoo'} 或 host: {'[style.color]': 'color'} )

1. Map 綁定(例如,host: {'[class]': 'classExpr'} 或者 host: {'[style]': 'styleExpr'} )

2. 靜態(tài)值(例如,host: {'class': 'foo'} 或 host: {'style': 'color: blue'} )

  1. 組件宿主綁定

  1. 屬性綁定(例如,host: {'[class.foo]': 'hasFoo'} 或 host: {'[style.color]': 'color'} )

1. Map 綁定(例如,host: {'[class]': 'classExpr'} 或者 host: {'[style]': 'styleExpr'} )

2. 靜態(tài)值(例如,host: {'class': 'foo'} 或 host: {'style': 'color: blue'} )

某個(gè)類或樣式綁定越具體,它的優(yōu)先級(jí)就越高。

對(duì)具體類(例如 [class.foo] )的綁定優(yōu)先于一般化的 [class] 綁定,對(duì)具體樣式(例如 [style.bar] )的綁定優(yōu)先于一般化的 [style] 綁定。

Path:"src/app/app.component.html"

<h3>Basic specificity</h3>


<!-- The `class.special` binding will override any value for the `special` class in `classExpr`.  -->
<div [class.special]="isSpecial" [class]="classExpr">Some text.</div>


<!-- The `style.color` binding will override any value for the `color` property in `styleExpr`.  -->
<div [style.color]="color" [style]="styleExpr">Some text.</div>

當(dāng)處理不同來源的綁定時(shí),也適用這種基于具體度的規(guī)則。 某個(gè)元素可能在聲明它的模板中有一些綁定、在所匹配的指令中有一些宿主綁定、在所匹配的組件中有一些宿主綁定。

模板中的綁定是最具體的,因?yàn)樗鼈冎苯硬⑶椅ㄒ坏貞?yīng)用于該元素,所以它們具有最高的優(yōu)先級(jí)。

指令的宿主綁定被認(rèn)為不太具體,因?yàn)橹噶羁梢栽诙鄠€(gè)位置使用,所以它們的優(yōu)先級(jí)低于模板綁定。

指令經(jīng)常會(huì)增強(qiáng)組件的行為,所以組件的宿主綁定優(yōu)先級(jí)最低。

Path:"src/app/app.component.html"

<h3>Source specificity</h3>


<!-- The `class.special` template binding will override any host binding to the `special` class set by `dirWithClassBinding` or `comp-with-host-binding`.-->
<comp-with-host-binding [class.special]="isSpecial" dirWithClassBinding>Some text.</comp-with-host-binding>


<!-- The `style.color` template binding will override any host binding to the `color` property set by `dirWithStyleBinding` or `comp-with-host-binding`. -->
<comp-with-host-binding [style.color]="color" dirWithStyleBinding>Some text.</comp-with-host-binding>

另外,綁定總是優(yōu)先于靜態(tài)屬性。

在下面的例子中,class[class] 具有相似的具體度,但 [class] 綁定優(yōu)先,因?yàn)樗莿?dòng)態(tài)的。

Path:"src/app/app.component.html"

<h3>Dynamic vs static</h3>


<!-- If `classExpr` has a value for the `special` class, this value will override the `class="special"` below -->
<div class="special" [class]="classExpr">Some text.</div>


<!-- If `styleExpr` has a value for the `color` property, this value will override the `style="color: blue"` below -->
<div style="color: blue" [style]="styleExpr">Some text.</div>

委托優(yōu)先級(jí)較低的樣式

更高優(yōu)先級(jí)的樣式可以使用 undefined 值“委托”給低級(jí)的優(yōu)先級(jí)樣式。雖然把 style 屬性設(shè)置為 null 可以確保該樣式被移除,但把它設(shè)置為 undefined 會(huì)導(dǎo)致 Angular 回退到該樣式的次高優(yōu)先級(jí)。

例如,考慮以下模板:

Path:"src/app/app.component.html"

<comp-with-host-binding dirWithHostBinding></comp-with-host-binding>

想象一下,dirWithHostBinding 指令和 comp-with-host-binding 組件都有 [style.width] 宿主綁定。在這種情況下,如果 dirWithHostBinding 把它的綁定設(shè)置為 undefined,則 width 屬性將回退到 comp-with-host-binding 主機(jī)綁定的值。但是,如果 dirWithHostBinding 把它的綁定設(shè)置為 null,那么 width 屬性就會(huì)被完全刪除。

事件綁定 (event)

事件綁定允許你監(jiān)聽某些事件,比如按鍵、鼠標(biāo)移動(dòng)、點(diǎn)擊和觸屏。

Angular 的事件綁定語法由等號(hào)左側(cè)帶圓括號(hào)的目標(biāo)事件和右側(cè)引號(hào)中的模板語句組成。 下面事件綁定監(jiān)聽按鈕的點(diǎn)擊事件。每當(dāng)點(diǎn)擊發(fā)生時(shí),都會(huì)調(diào)用組件的 onSave() 方法。

目標(biāo)事件

如前所述,其目標(biāo)就是此按鈕的單擊事件。

Path:"src/app/app.component.html"

<button (click)="onSave($event)">Save</button>

有些人更喜歡帶 on- 前綴的備選形式,稱之為規(guī)范形式:

Path:"src/app/app.component.html"

<button on-click="onSave($event)">on-click Save</button>

元素事件可能是更常見的目標(biāo),但 Angular 會(huì)先看這個(gè)名字是否能匹配上已知指令的事件屬性,就像下面這個(gè)例子:

Path:"src/app/app.component.html"

<h4>myClick is an event on the custom ClickDirective:</h4>
<button (myClick)="clickMessage=$event" clickable>click with myClick</button>
{{clickMessage}}

如果這個(gè)名字沒能匹配到元素事件或已知指令的輸出屬性,Angular 就會(huì)報(bào)“未知指令”錯(cuò)誤。

$event 和事件處理語句

在事件綁定中,Angular 會(huì)為目標(biāo)事件設(shè)置事件處理器。

當(dāng)事件發(fā)生時(shí),這個(gè)處理器會(huì)執(zhí)行模板語句。 典型的模板語句通常涉及到響應(yīng)事件執(zhí)行動(dòng)作的接收器,例如從 HTML 控件中取得值,并存入模型。

綁定會(huì)通過名叫 $event 的事件對(duì)象傳遞關(guān)于此事件的信息(包括數(shù)據(jù)值)。

事件對(duì)象的形態(tài)取決于目標(biāo)事件。如果目標(biāo)事件是原生 DOM 元素事件, $event 就是 DOM 事件對(duì)象,它有像 targettarget.value 這樣的屬性。

考慮這個(gè)示例:

Path:"src/app/app.component.html"

<input [value]="currentItem.name"
       (input)="currentItem.name=$event.target.value" >
without NgModel

上面的代碼在把輸入框的 value 屬性綁定到 name 屬性。 要監(jiān)聽對(duì)值的修改,代碼綁定到輸入框的 input 事件。 當(dāng)用戶造成更改時(shí),input 事件被觸發(fā),并在包含了 DOM 事件對(duì)象 ($event) 的上下文中執(zhí)行這條語句。

要更新 name 屬性,就要通過路徑 $event.target.value 來獲取更改后的值。

如果事件屬于指令(回想一下,組件是指令的一種),那么 $event 具體是什么由指令決定。

使用 EventEmitter 實(shí)現(xiàn)自定義事件

通常,指令使用 Angular EventEmitter 來觸發(fā)自定義事件。 指令創(chuàng)建一個(gè) EventEmitter 實(shí)例,并且把它作為屬性暴露出來。 指令調(diào)用 EventEmitter.emit(payload) 來觸發(fā)事件,可以傳入任何東西作為消息載荷。 父指令通過綁定到這個(gè)屬性來監(jiān)聽事件,并通過 $event 對(duì)象來訪問載荷。

假設(shè) ItemDetailComponent 用于顯示英雄的信息,并響應(yīng)用戶的動(dòng)作。 雖然 ItemDetailComponent 包含刪除按鈕,但它自己并不知道該如何刪除這個(gè)英雄。 最好的做法是觸發(fā)事件來報(bào)告“刪除用戶”的請(qǐng)求。

下面的代碼節(jié)選自 ItemDetailComponent:

Path:"src/app/item-detail/item-detail.component.html (template)"

<img src="{{itemImageUrl}}" [style.display]="displayNone">
<span [style.text-decoration]="lineThrough">{{ item.name }}
</span>
<button (click)="delete()">Delete</button>

Path:"src/app/item-detail/item-detail.component.ts (deleteRequest)"

// This component makes a request but it can't actually delete a hero.
@Output() deleteRequest = new EventEmitter<Item>();


delete() {
  this.deleteRequest.emit(this.item);
  this.displayNone = this.displayNone ? '' : 'none';
  this.lineThrough = this.lineThrough ? '' : 'line-through';
}

組件定義了 deleteRequest 屬性,它是 EventEmitter 實(shí)例。 當(dāng)用戶點(diǎn)擊刪除時(shí),組件會(huì)調(diào)用 delete() 方法,讓 EventEmitter 發(fā)出一個(gè) Item 對(duì)象。

現(xiàn)在,假設(shè)有個(gè)宿主的父組件,它綁定了 ItemDetailComponentdeleteRequest 事件。

Path:"src/app/app.component.html (event-binding-to-component)"

<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>

當(dāng) deleteRequest 事件觸發(fā)時(shí),Angular 調(diào)用父組件的 deleteItem 方法, 在 $event 變量中傳入要?jiǎng)h除的英雄(來自 ItemDetail)。

模板語句有副作用

雖然模板表達(dá)式不應(yīng)該有副作用,但是模板語句通常會(huì)有。這里的 deleteItem() 方法就有一個(gè)副作用:它刪除了一個(gè)條目。

刪除這個(gè)英雄會(huì)更新模型,還可能觸發(fā)其它修改,包括向遠(yuǎn)端服務(wù)器的查詢和保存。 這些變更通過系統(tǒng)進(jìn)行擴(kuò)散,并最終顯示到當(dāng)前以及其它視圖中。

雙向綁定 [(...)]

雙向綁定為你的應(yīng)用程序提供了一種在組件類及其模板之間共享數(shù)據(jù)的方式。

雙向綁定的基礎(chǔ)知識(shí)

雙向綁定會(huì)做兩件事:

  1. 設(shè)置特定的元素屬性。

  1. 監(jiān)聽元素的變更事件。

Angular 為此提供了一種特殊的雙向數(shù)據(jù)綁定語法 [()]。[()] 語法將屬性綁定的括號(hào) [] 與事件綁定的括號(hào) () 組合在一起。

[()] 語法很容易想明白:該元素具有名為 x 的可設(shè)置屬性和名為 xChange 的相應(yīng)事件。 SizerComponent 就是用的這種模式。它具有一個(gè)名為 size 的值屬性和一個(gè)與之相伴的 sizeChange 事件:

Path:"src/app/sizer.component.ts"

import { Component, Input, Output, EventEmitter } from '@angular/core';


@Component({
  selector: 'app-sizer',
  templateUrl: './sizer.component.html',
  styleUrls: ['./sizer.component.css']
})
export class SizerComponent {




  @Input()  size: number | string;
  @Output() sizeChange = new EventEmitter<number>();


  dec() { this.resize(-1); }
  inc() { this.resize(+1); }


  resize(delta: number) {
    this.size = Math.min(40, Math.max(8, +this.size + delta));
    this.sizeChange.emit(this.size);
  }


}

Path:"src/app/sizer.component.html"

<div>
  <button (click)="dec()" title="smaller">-</button>
  <button (click)="inc()" title="bigger">+</button>
  <label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>

size 的初始值來自屬性綁定的輸入值。單擊按鈕可在最小值/最大值范圍內(nèi)增大或減小 size,然后帶上調(diào)整后的大小發(fā)出 sizeChange 事件。

下面的例子中,AppComponent.fontSize 被雙向綁定到 SizerComponent

Path:"src/app/app.component.html (two-way-1)"

<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>

AppComponent.fontSizePx 建立初始 SizerComponent.size 值。

Path:"src/app/app.component.ts"

fontSizePx = 16;

單擊按鈕就會(huì)通過雙向綁定更新 AppComponent.fontSizePx。修改后的 AppComponent.fontSizePx 值將傳遞到樣式綁定,從而使顯示的文本更大或更小。

雙向綁定語法實(shí)際上是屬性綁定和事件綁定的語法糖。 Angular 將 izerComponent 的綁定分解成這樣:

Path:"src/app/app.component.html (two-way-2)"

<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>

$event 變量包含了 SizerComponent.sizeChange 事件的荷載。 當(dāng)用戶點(diǎn)擊按鈕時(shí),Angular 將 $event 賦值給 AppComponent.fontSizePx

表單中的雙向綁定

與單獨(dú)的屬性綁定和事件綁定相比,雙向綁定語法非常方便。將雙向綁定與 HTML 表單元素(例如 <input><select>)一起使用會(huì)很方便。但是,沒有哪個(gè)原生 HTML 元素會(huì)遵循 x 值和 xChange 事件的命名模式。

內(nèi)置指令

Angular 提供了兩種內(nèi)置指令:屬性型指令和結(jié)構(gòu)型指令。

內(nèi)置屬性型指令

屬性型指令會(huì)監(jiān)聽并修改其它 HTML 元素和組件的行為、AttributeProperty。 它們通常被應(yīng)用在元素上,就好像它們是 HTML 屬性一樣,因此得名屬性型指令。

許多 NgModule(例如 RouterModuleFormsModule )都定義了自己的屬性型指令。最常見的屬性型指令如下:

  1. NgClass —— 添加和刪除一組 CSS 類。

用 ngClass 同時(shí)添加或刪除幾個(gè) CSS 類。

Path:"src/app/app.component.html"

    <!-- toggle the "special" class on/off with a property -->
    <div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>

注:
- 要添加或刪除單個(gè)類,請(qǐng)使用類綁定而不是 NgClass

考慮一個(gè) setCurrentClasses() 組件方法,該方法設(shè)置一個(gè)組件屬性 currentClasses,該對(duì)象具有一個(gè)根據(jù)其它三個(gè)組件屬性的 true / false 狀態(tài)來添加或刪除三個(gè) CSS 類的對(duì)象。該對(duì)象的每個(gè)鍵(key)都是一個(gè) CSS 類名。如果要添加上該類,則其值為 true,反之則為 false。

Path:"src/app/app.component.html"

    currentClasses: {};
    setCurrentClasses() {
      // CSS classes: added/removed per current state of component properties
      this.currentClasses =  {
        'saveable': this.canSave,
        'modified': !this.isUnchanged,
        'special':  this.isSpecial
      };
    }

NgClass 屬性綁定到 currentClasses,根據(jù)它來設(shè)置此元素的 CSS 類:

Path:"src/app/app.component.html"

    <div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>

注:
- 請(qǐng)記住,在這種情況下,你要在初始化時(shí)和它依賴的屬性發(fā)生變化時(shí)調(diào)用 setCurrentClasses()

  1. NgStyle —— 添加和刪除一組 HTML 樣式。

使用 NgStyle 根據(jù)組件的狀態(tài)同時(shí)動(dòng)態(tài)設(shè)置多個(gè)內(nèi)聯(lián)樣式。

不用 NgStyle

有些情況下,要考慮使用樣式綁定來設(shè)置單個(gè)樣式值,而不使用 NgStyle。

Path:"src/app/app.component.html"

    <div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
      This div is x-large or smaller.
    </div>

但是,如果要同時(shí)設(shè)置多個(gè)內(nèi)聯(lián)樣式,請(qǐng)使用 NgStyle 指令。

下面的例子是一個(gè) setCurrentStyles() 方法,它基于該組件另外三個(gè)屬性的狀態(tài),用一個(gè)定義了三個(gè)樣式的對(duì)象設(shè)置了 currentStyles 屬性。

Path:"src/app/app.component.ts"

    currentStyles: {};
    setCurrentStyles() {
      // CSS styles: set per current state of component properties
      this.currentStyles = {
        'font-style':  this.canSave      ? 'italic' : 'normal',
        'font-weight': !this.isUnchanged ? 'bold'   : 'normal',
        'font-size':   this.isSpecial    ? '24px'   : '12px'
      };
    }

ngStyle 屬性綁定到 currentStyles,來根據(jù)它設(shè)置此元素的樣式:

Path:"src/app/app.component.html"

    <div [ngStyle]="currentStyles">
      This div is initially italic, normal weight, and extra large (24px).
    </div>

注:
- 請(qǐng)記住,無論是在初始時(shí)還是其依賴的屬性發(fā)生變化時(shí),都要調(diào)用 setCurrentStyles()

  1. NgModel —— 將數(shù)據(jù)雙向綁定添加到 HTML 表單元素。

NgModel 指令允許你顯示數(shù)據(jù)屬性并在用戶進(jìn)行更改時(shí)更新該屬性。這是一個(gè)例子:

Path:"src/app/app.component.html (NgModel example)"

    <label for="example-ngModel">[(ngModel)]:</label>
    <input [(ngModel)]="currentItem.name" id="example-ngModel">

導(dǎo)入 FormsModule 以使用 ngModel

要想在雙向數(shù)據(jù)綁定中使用 ngModel 指令,必須先導(dǎo)入 FormsModule 并將其添加到 NgModuleimports 列表中。要了解關(guān)于 FormsModulengModel 的更多信息,參見表單一章。

記住,要導(dǎo)入 FormsModule 才能讓 [(ngModel)] 可用,如下所示:

Path:"src/app/app.module.ts (FormsModule import)"

    import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
    /* . . . */
    @NgModule({
    /* . . . */


      imports: [
        BrowserModule,
        FormsModule // <--- import into the NgModule
      ],
    /* . . . */
    })
    export class AppModule { }

通過分別綁定到 <input> 元素的 value 屬性和 input 事件,可以達(dá)到同樣的效果:

Path:"src/app/app.component.html"

    <label for="without">without NgModel:</label>
    <input [value]="currentItem.name" (input)="currentItem.name=$event.target.value" id="without">

為了簡(jiǎn)化語法,ngModel 指令把技術(shù)細(xì)節(jié)隱藏在其輸入屬性 ngModel 和輸出屬性 ngModelChange 的后面:

Path:"src/app/app.component.html"

    <label for="example-change">(ngModelChange)="...name=$event":</label>
    <input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">

ngModel 輸入屬性會(huì)設(shè)置該元素的值,并通過 ngModelChange 的輸出屬性來監(jiān)聽元素值的變化。

NgModel 和值訪問器

這些技術(shù)細(xì)節(jié)是針對(duì)每種具體元素的,因此 NgModel 指令僅適用于通過 ControlValueAccessor 適配過這種協(xié)議的元素。Angular 已經(jīng)為所有基本的 HTML 表單元素提供了值訪問器,表單一章示范了如何綁定到它們。

在編寫適當(dāng)?shù)闹翟L問器之前,不能將 [(ngModel)] 應(yīng)用于非表單的原生元素或第三方自定義組件。欲知詳情,參見DefaultValueAccessor上的 API 文檔。

你不一定非用為所編寫的 Angular 組件提供值訪問器,因?yàn)槟氵€可以把值屬性和事件屬性命名為符合 Angular 的基本雙向綁定語法的形式,并完全跳過 NgModel。雙向綁定部分的 sizer 是此技術(shù)的一個(gè)示例。

單獨(dú)的 ngModel 綁定是對(duì)綁定到元素的原生屬性方式的一種改進(jìn),但你可以使用 [(ngModel)] 語法來通過單個(gè)聲明簡(jiǎn)化綁定:

Path:"src/app/app.component.html"

    <label for="example-ngModel">[(ngModel)]:</label>
    <input [(ngModel)]="currentItem.name" id="example-ngModel">

[(ngModel)] 語法只能設(shè)置數(shù)據(jù)綁定屬性。如果你要做得更多,可以編寫擴(kuò)展表單。例如,下面的代碼將 <input> 值更改為大寫:

Path:"src/app/app.component.html"

    <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">

這里是所有這些變體的動(dòng)畫,包括這個(gè)大寫轉(zhuǎn)換的版本:

內(nèi)置結(jié)構(gòu)型指令

結(jié)構(gòu)型指令的職責(zé)是 HTML 布局。 它們塑造或重塑 DOM 的結(jié)構(gòu),這通常是通過添加、移除和操縱它們所附加到的宿主元素來實(shí)現(xiàn)的。

常見的內(nèi)置結(jié)構(gòu)型指令:

  1. NgIf —— 從模板中創(chuàng)建或銷毀子視圖。

你可以通過將 NgIf 指令應(yīng)用在宿主元素上來從 DOM 中添加或刪除元素。在此示例中,將指令綁定到了條件表達(dá)式,例如 isActive

Path:"src/app/app.component.html"

    <app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>

注:
- 不要忘了 ngIf 前面的星號(hào)(*)。

當(dāng) isActive 表達(dá)式返回真值時(shí),NgIf 會(huì)把 ItemDetailComponent 添加到 DOM 中。當(dāng)表達(dá)式為假值時(shí),NgIf 將從 DOM 中刪除 ItemDetailComponent,從而銷毀該組件及其所有子組件。

顯示/隱藏與 NgIf

隱藏元素與使用 NgIf 刪除元素不同。為了進(jìn)行比較,下面的示例演示如何使用類或樣式綁定來控制元素的可見性。

Path:"src/app/app.component.html"

    <!-- isSpecial is true -->
    <div [class.hidden]="!isSpecial">Show with class</div>
    <div [class.hidden]="isSpecial">Hide with class</div>


    <p>ItemDetail is in the DOM but hidden</p>
    <app-item-detail [class.hidden]="isSpecial"></app-item-detail>


    <div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
    <div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div>

隱藏元素時(shí),該元素及其所有后代仍保留在 DOM 中。這些元素的所有組件都保留在內(nèi)存中,Angular 會(huì)繼續(xù)做變更檢查。它可能會(huì)占用大量計(jì)算資源,并且會(huì)不必要地降低性能。

NgIf 工作方式有所不同。如果 NgIffalse,則 Angular 將從 DOM 中刪除該元素及其后代。這銷毀了它們的組件,釋放了資源,從而帶來更好的用戶體驗(yàn)。

如果要隱藏大型組件樹,請(qǐng)考慮使用 NgIf 作為顯示/隱藏的更有效替代方法。

防范空指針錯(cuò)誤

ngIf 另一個(gè)優(yōu)點(diǎn)是你可以使用它來防范空指針錯(cuò)誤。顯示/隱藏就是最合適的極簡(jiǎn)用例,當(dāng)你需要防范時(shí),請(qǐng)改用 ngIf 代替。如果其中嵌套的表達(dá)式嘗試訪問 null 的屬性,Angular 將引發(fā)錯(cuò)誤。

下面的例子中 NgIf 保護(hù)著兩個(gè) <div>。僅當(dāng)存在 currentCustomer 時(shí),才會(huì)顯示 currentCustomer 名稱。除非它為 null 否則不會(huì)顯示 nullCustomer。

Path:"src/app/app.component.html"

    <div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>

Path:"src/app/app.component.html"

    <div *ngIf="nullCustomer">Hello, <span>{{nullCustomer}}</span></div>

  1. NgFor —— 為列表中的每個(gè)條目重復(fù)渲染一個(gè)節(jié)點(diǎn)。

NgFor 是一個(gè)重復(fù)器指令 —— 一種用來顯示條目列表的方法。你定義了一個(gè) HTML 塊,該 HTML 塊定義了應(yīng)如何顯示單個(gè)條目,然后告訴 Angular 以該塊為模板來渲染列表中的每個(gè)條目。賦值給 *ngFor 的文本是用來指導(dǎo)重復(fù)器工作過程的指令。

以下示例顯示了如何將 NgFor 應(yīng)用于簡(jiǎn)單的 <div>。(不要忘了 ngFor 前面的星號(hào)(*)。)

Path:"src/app/app.component.html"

    <div *ngFor="let item of items">{{item.name}}</div>

你還可以將 NgFor 應(yīng)用于組件元素,如以下示例所示。

Path:"src/app/app.component.html"

    <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>

*NGFOR 微語法
&賦值給 *ngFor 的字符串不是模板表達(dá)式。而是一個(gè)微語法 —— 由 Angular 解釋的一種小型語言。字符串 "let item of items" 的意思是:

&&將 items 數(shù)組中的每個(gè)條目存儲(chǔ)在局部循環(huán)變量 item 中,并使其可用于每次迭代的模板 HTML 中。

&Angular 將該指令轉(zhuǎn)換為包裹著宿主元素的 <ng-template&,然后反復(fù)使用此模板為列表中的每個(gè) item 創(chuàng)建一組新的元素和綁定。

模板輸入變量

item 前面的 let 關(guān)鍵字創(chuàng)建了一個(gè)名為 item 的模板輸入變量。ngFor 指令迭代父組件的 items 屬性所返回的 items 數(shù)組,并在每次迭代期間將 item 設(shè)置為該數(shù)組中的當(dāng)前條目。

ngFor 的宿主元素及其后代中可引用 item,來訪問該條目的屬性。以下示例首先在插值中引用 item,然后把一個(gè)綁定表達(dá)式傳入 <app-item-detail> 組件的 item 屬性。

Path:"src/app/app.component.html"

    <div *ngFor="let item of items">{{item.name}}</div>
    <!-- . . . -->
      <app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>

*ngFor 與 index

NgFor 指令上下文中的 index 屬性在每次迭代中返回該條目的從零開始的索引。 你可以在模板輸入變量中捕獲 index,并在模板中使用它。

下面的例子在名為 i 的變量中捕獲 index,并將其與條目名稱一起顯示。

Path:"src/app/app.component.ts"

    <div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>

*帶 trackBy 的 ngFor**

如果將 NgFor 與大型列表一起使用,則對(duì)某個(gè)條目的較小更改(例如刪除或添加一項(xiàng))就會(huì)觸發(fā)一系列 DOM 操作。 例如,重新查詢服務(wù)器可能會(huì)重置包含所有新條目對(duì)象的列表,即使先前已顯示這些條目也是如此。在這種情況下,Angular 只能看到由新的對(duì)象引用組成的新列表,它別無選擇,只能用所有新的 DOM 元素替換舊的 DOM 元素。

你可以使用 trackBy 來讓它更加高效。向該組件添加一個(gè)方法,該方法返回 NgFor 應(yīng)該跟蹤的值。這個(gè)例子中,該值是英雄的 id。如果 id 已經(jīng)被渲染,Angular 就會(huì)跟蹤它,而不會(huì)重新向服務(wù)器查詢相同的 id。

Path:"src/app/app.component.html"

    <div *ngFor="let item of items; trackBy: trackByItems">
      ({{item.id}}) {{item.name}}
    </div>

這就是 trackBy 效果的說明?!癛eset items” 將創(chuàng)建具有相同 item.id 的新條目?!癈hange ids” 將使用新的 item.id 創(chuàng)建新條目。

如果沒有 trackBy,這些按鈕都會(huì)觸發(fā)完全的 DOM 元素替換。

有了 trackBy,則只有修改了 id 的按鈕才會(huì)觸發(fā)元素替換。

注:
- 內(nèi)置指令僅僅使用了公共 API。也就是說,它們沒有用到任何其它指令無權(quán)訪問的私有 API。

  1. NgSwitch —— 一組在備用視圖之間切換的指令。

NgSwitch 類似于 JavaScript switch 語句。它根據(jù)切換條件顯示幾個(gè)可能的元素中的一個(gè)。Angular 只會(huì)將選定的元素放入 DOM。

NgSwitch 實(shí)際上是三個(gè)協(xié)作指令的集合: NgSwitch,NgSwitchCaseNgSwitchDefault,如以下示例所示。

Path:"src/app/app.component.html"

    <div [ngSwitch]="currentItem.feature">
      <app-stout-item    *ngSwitchCase="'stout'"    [item]="currentItem"></app-stout-item>
      <app-device-item   *ngSwitchCase="'slim'"     [item]="currentItem"></app-device-item>
      <app-lost-item     *ngSwitchCase="'vintage'"  [item]="currentItem"></app-lost-item>
      <app-best-item     *ngSwitchCase="'bright'"   [item]="currentItem"></app-best-item>
    <!-- . . . -->
      <app-unknown-item  *ngSwitchDefault           [item]="currentItem"></app-unknown-item>
    </div>

NgSwitch 是控制器指令。把它綁定到一個(gè)返回開關(guān)值的表達(dá)式,例如 feature。盡管此示例中的 feature 值是字符串,但開關(guān)值可以是任何類型。

綁定到 [ngSwitch]。如果試圖寫成 *ngSwitch,就會(huì)出現(xiàn)錯(cuò)誤,因?yàn)?NgSwitch 是屬性型指令,而不是結(jié)構(gòu)型指令。它不會(huì)直接接觸 DOM,而是會(huì)更改與之相伴的指令的行為。

綁定到 *ngSwitchCase*ngSwitchDefault 、NgSwitchCaseNgSwitchDefault 指令都是結(jié)構(gòu)型指令,因?yàn)樗鼈儠?huì)從 DOM 中添加或移除元素。

  • 當(dāng) NgSwitchCase 的綁定值等于開關(guān)值時(shí),就將其元素添加到 DOM 中;否則從 DOM 中刪除。

  • NgSwitchDefault 會(huì)在沒有任何一個(gè) NgSwitchCase 被選中時(shí)把它所在的元素加入 DOM 中。

開關(guān)指令對(duì)于添加和刪除組件元素特別有用。本示例在 "item-switch.components.ts" 文件中定義的四個(gè) item 組件之間切換。每個(gè)組件都有一個(gè)名叫 item 的輸入屬性,它會(huì)綁定到父組件的 currentItem

開關(guān)指令也同樣適用于原生元素和 Web Component。 比如,你可以把 <app-best-item> 分支替換為如下代碼。

Path:"src/app/app.component.html"

    <div *ngSwitchCase="'bright'"> Are you as bright as {{currentItem.name}}?</div>

結(jié)構(gòu)型指令一小節(jié)涵蓋了結(jié)構(gòu)型指令的詳細(xì)內(nèi)容,它解釋了以下內(nèi)容:

  • 為什么在要指令名稱前加上星號(hào)(*)。

  • 當(dāng)指令沒有合適的宿主元素時(shí),使用 <ng-container& 對(duì)元素進(jìn)行分組。

  • 如何寫自己的結(jié)構(gòu)型指令。

  • 你只能往一個(gè)元素上應(yīng)用一個(gè)結(jié)構(gòu)型指令。

模板引用變量( #var )

模板引用變量通常是對(duì)模板中 DOM 元素的引用。它還可以引用指令(包含組件)、元素、TemplateRef 或 Web Component。

使用井號(hào)(#)聲明模板引用變量。以下模板引用變量 #phone 會(huì)在 <input> 元素上聲明了一個(gè) phone 變量。

Path:"src/app/app.component.html"

<input #phone placeholder="phone number" />

你可以在組件模板中的任何位置引用模板引用變量。這個(gè)例子中,模板下方的 <button> 就引用了 phone 變量。

Path:"src/app/app.component.html"

<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>

模板引用變量如何取得它本身的值

在大多數(shù)情況下,Angular 會(huì)將模板引用變量的值設(shè)置為聲明該變量的元素。在上一個(gè)示例中,phone 指的是電話號(hào)碼的 <input>。按鈕的單擊處理程序?qū)堰@個(gè) <input> 的值傳給組件的 callPhone() 方法。

NgForm 指令可以更改該行為并將該值設(shè)置為其它值。在以下示例中,模板引用變量 itemForm 出現(xiàn)了 3 次,由 HTML 分隔。

Path:"src/app/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>

當(dāng) itemForm 的引用沒有 "ngForm" 值時(shí),它將是 HTMLFormElement。不過,組件和指令之間的區(qū)別在于,在不指定屬性值的情況下組件將引用自身(隱式引用),而指令不會(huì)更改隱式引用(仍為所在元素)。

但是,帶有 NgForm 時(shí),itemForm 就是對(duì) NgForm 指令的引用,它能夠跟蹤表單中每個(gè)控件的值和有效性。

原生 <form> 元素沒有 form 屬性,但 NgForm 指令有,這樣就能在 itemForm.form.valid 無效的情況下禁用提交按鈕,并將整個(gè)表單控制樹傳給父組件的 onSubmit() 方法。

對(duì)模板引用變量的思考

模板引用變量(#phone)與模板輸入變量(let phone)不同。

模板引用變量的范圍是整個(gè)模板。因此,不要在同一模板中多次定義相同的變量名,因?yàn)樗谶\(yùn)行時(shí)的值將不可預(yù)測(cè)。

替代語法 你也可以用 ref- 前綴代替 #。 下面的例子中就用把 fax 變量聲明成了 ref-fax 而不是 #fax

Path:"src/app/app.component.html"

<input ref-fax placeholder="fax number" />
<button (click)="callFax(fax.value)">Fax</button>

輸入和輸出屬性

@Input()@Output() 允許 Angular 在其父上下文和子指令或組件之間共享數(shù)據(jù)。@Input() 屬性是可寫的,而 @Output() 屬性是可觀察對(duì)象。

考慮以下父子關(guān)系示例:

<parent-component>
  <child-component></child-component>
</parent-component>

在這里,<child-component> 選擇器或子指令嵌入在 <parent-component> 中,用作子級(jí)上下文。

@Input()@Output() 充當(dāng)子組件的 API 或應(yīng)用編程接口,因?yàn)樗鼈冊(cè)试S子組件與父組件進(jìn)行通信。可以把 @Input()@Output() 看做港口或門,@Input() 是進(jìn)入組件的門,允許數(shù)據(jù)流入,而 @Output() 是離開組件的門,允許子組件向外發(fā)出數(shù)據(jù)。

關(guān)于 @Input()@Output() 這一部分有其自己的現(xiàn)場(chǎng)演練 / 下載范例。以下小節(jié)將重點(diǎn)介紹示例應(yīng)用程序中的關(guān)鍵點(diǎn)。

@Input() 和 @Output() 是獨(dú)立的

&盡管 @Input()@Output() 通常在應(yīng)用程序中同時(shí)出現(xiàn),但是你可以單獨(dú)使用它們。如果嵌套組件只需要向其父級(jí)發(fā)送數(shù)據(jù),則不需要 @Input(),而只需 @Output()。反之亦然,如果子級(jí)只需要從父級(jí)接收數(shù)據(jù),則只需要 @Input()

如何使用 @Input()

在子組件或指令中使用 @Input() 裝飾器,可以讓 Angular 知道該組件中的屬性可以從其父組件中接收值。這很好記,因?yàn)檫@種數(shù)據(jù)流是從子組件的角度來看就是輸入。因此,@Input() 允許將數(shù)據(jù)從父組件輸入到子組件中。

為了說明 @Input() 的用法,請(qǐng)編輯應(yīng)用程序的以下部分:

  • 子組件類及其模板

  • 父組件類及其模板

在子組件中

要在子組件類中使用 @Input() 裝飾器,請(qǐng)首先導(dǎo)入 Input,然后使用 @Input() 來裝飾一個(gè)屬性:

Path:"src/app/item-detail/item-detail.component.ts"

import { Component, Input } from '@angular/core'; // First, import Input
export class ItemDetailComponent {
  @Input() item: string; // decorate the property with @Input()
}

在這個(gè)例子中,@Input() 裝飾具有 string 類型的屬性 item,但是,@Input() 屬性可以具有任何類型,例如 number,string,booleanobject。item 的值會(huì)來自下一部分要介紹的父組件。

接下來,在子組件模板中,添加以下內(nèi)容:

Path:"src/app/item-detail/item-detail.component.html"

<p>
  Today's item: {{item}}
</p>

在父組件中

下一步是在父組件的模板中綁定該屬性。在此示例中,父組件模板是 "app.component.html"。

首先,使用子組件的選擇器(這里是 <app-item-detail> )作為父組件模板中的指令。然后,使用屬性綁定將子組件中的屬性綁定到父組件中的屬性。

Path:"src/app/app.component.html"

<app-item-detail [item]="currentItem"></app-item-detail>

接下來,在父組件類 "app.component.ts" 中,為 currentItem 指定一個(gè)值:

Path:"src/app/app.component.ts"

export class AppComponent {
  currentItem = 'Television';
}

借助 @Input(),Angular 將 currentItem 的值傳給子級(jí),以便該 item 渲染為 Television

方括號(hào) [] 中的目標(biāo)是子組件中帶有 @Input() 裝飾器的屬性。綁定源(等號(hào)右邊的部分)是父組件要傳給內(nèi)嵌組件的數(shù)據(jù)。

關(guān)鍵是,當(dāng)要在父組件中綁定到子組件中的屬性(即方括號(hào)中的內(nèi)容)時(shí),必須在子組件中使用 @Input()來裝飾該屬性。

OnChanges 和 @Input()

&要監(jiān)視 @Input() 屬性的更改,請(qǐng)使用 Angular 的生命周期鉤子之一 OnChanges。OnChanges 是專門設(shè)計(jì)用于具有 @Input() 裝飾器的屬性的。

如何使用 @Output()

在子組件或指令中使用 @Output() 裝飾器,允許數(shù)據(jù)從子級(jí)流出到父級(jí)。

通常應(yīng)將 @Output() 屬性初始化為 Angular EventEmitter,并將值作為事件從組件中向外流出。

就像 @Input() 一樣,你也要在子組件的屬性上使用 @Output(),但其類型為 EventEmitter。

@Output() 將子組件中的屬性標(biāo)記為一扇門,數(shù)據(jù)可以通過這扇門從子組件傳到父組件。 然后,子組件必須引發(fā)一個(gè)事件,以便父組件知道發(fā)生了某些變化。為了引發(fā)事件,@Output() 要和 EventEmitter 配合使用,EventEmitter@angular/core 中的一個(gè)類,用于發(fā)出自定義事件。

要使用 @Output(),請(qǐng)編輯應(yīng)用程序的以下部分:

  • 子組件類及其模板

  • 父組件類及其模板

下面的示例演示了如何在子組件中設(shè)置 @Output(),以將你在 HTML 的 <input> 中輸入數(shù)據(jù),并將其追加到父組件中的數(shù)組里。

在子組件中

此示例有一個(gè) <input>,用戶可以在其中輸入一個(gè)值并單擊引發(fā)事件的 <button>。然后,通過 EventEmitter 將數(shù)據(jù)轉(zhuǎn)給父組件。

首先,請(qǐng)確保在子組件類中導(dǎo)入 OutputEventEmitter

import { Output, EventEmitter } from '@angular/core';

接下來,仍然在子組件中,使用組件類中的 @Output() 裝飾屬性。下面例子中的 @Output() 名叫 newItemEvent,其類型是 EventEmitter,這表示它是一個(gè)事件。

Path:"src/app/item-output/item-output.component.ts"

@Output() newItemEvent = new EventEmitter<string>();

上述聲明的不同之處如下:

  • @Output() —— 一個(gè)裝飾器函數(shù),它將該屬性標(biāo)記為把數(shù)據(jù)從子級(jí)傳遞到父級(jí)的一種方式

  • newItemEvent@Output() 的名字

-EventEmitter<string> — @Output() 的類型

  • new EventEmitter<string>() 告訴 Angular 創(chuàng)建一個(gè)新的事件發(fā)射器,并且它發(fā)射的數(shù)據(jù)為 string 類型。該類型也可以是任何類型,例如 numberboolean 等。有關(guān) EventEmitter 的更多信息,請(qǐng)參閱 EventEmitter API 文檔。

接下來,在同一個(gè)組件類中創(chuàng)建一個(gè) addNewItem() 方法:

Path:"src/app/item-output/item-output.component.ts"

export class ItemOutputComponent {


  @Output() newItemEvent = new EventEmitter<string>();


  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}

addNewItem() 函數(shù)使用 @Output() newItemEvent 引發(fā)一個(gè)事件,在該事件中它將發(fā)出用戶鍵入到 <input> 中的內(nèi)容。換句話說,當(dāng)用戶單擊 UI 中的 “Add” 按鈕時(shí),子組件會(huì)讓父組件知道該事件,并將該數(shù)據(jù)傳給父組件。

在子組件的模板中

子組件的模板中有兩個(gè)控件。第一個(gè)是帶有模板引用變量 #newItem 的 HTML <input>,用戶可在其中鍵入條目名稱。用戶鍵入到 <input> 中的內(nèi)容都存儲(chǔ)在 #newItem 變量中。

Path:"src/app/item-output/item-output.component.html"

<label>Add an item: <input #newItem></label>
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>

第二個(gè)元素是帶有事件綁定的 <button>。之所以知道這是事件綁定,是因?yàn)榈忍?hào)的左側(cè)部分在圓括號(hào)中 (click)。

(click) 事件綁定到子組件類中的 addNewItem() 方法,無論 #newItem 的值如何,該子組件類均將其作為參數(shù)。

現(xiàn)在,子組件已經(jīng)有了用于將數(shù)據(jù)發(fā)送到父組件的 @Output() 和引發(fā)事件的方法。下一步是在父組件中。

在父組件中

在此示例中,父組件是 AppComponent,但是你可以使用任何能嵌套子組件的組件。

此示例中的 AppComponent 具有數(shù)組型的 items 列表以及將更多條目添加到數(shù)組中的方法。

Path:"src/app/app.component.ts"

export class AppComponent {
  items = ['item1', 'item2', 'item3', 'item4'];


  addItem(newItem: string) {
    this.items.push(newItem);
  }
}

addItem() 方法接收字符串形式的參數(shù),然后將該字符串添加到 items 數(shù)組中。

在父組件的模板中

接下來,在父組件的模板中,將父組件的方法綁定到子組件的事件。將子組件選擇器(這里是 <app-item-output>)放在父組件的模板 "app.component.html" 中。

Path:"src/app/app.component.html"

<app-item-output (newItemEvent)="addItem($event)"></app-item-output>

事件綁定 (newItemEvent)='addItem($event)'告訴 Angular 將子組件的 newItemEvent 事件連接到父組件中的方法 addItem(),以及將子組件通知父組件的事件作為 addItem() 的參數(shù)。換句話說,這是實(shí)際傳遞數(shù)據(jù)的地方。$event 包含用戶在子模板 UI 中鍵入到 <input> 中的數(shù)據(jù)。

現(xiàn)在,為了查看 @Output() 工作情況,請(qǐng)將以下內(nèi)容添加到父組件的模板中:

<ul>
  <li *ngFor="let item of items">
      {{item}}
  </li>
</ul>

*ngFor 會(huì)遍歷 items 數(shù)組中的條目。當(dāng)你在子組件的 <input> 中輸入值并單擊按鈕時(shí),子組件將發(fā)出事件,父組件的 addItem() 方法將值推送到 items 數(shù)組,并將其渲染在列表中。

@Input() 和 @Output() 在一起

你可以在和下面代碼相同的子組件上使用 @Input()@Output()

Path:"src/app/app.component.html"

<app-input-output [item]="currentItem" (deleteRequest)="crossOffItem($event)"></app-input-output>

目標(biāo) item 是子組件類中的 @Input() 屬性,它從父組件的屬性 currentItem 中接收值。當(dāng)你單擊刪除時(shí),子組件將引發(fā)事件 deleteRequest,它攜帶的值將作為父組件的 crossOffItem() 方法的參數(shù)。

下圖是同一子組件上的 @Input()@Output(),并顯示了每個(gè)子組件的不同部分:

如圖所示,像分別使用它們那樣同時(shí)使用輸入和輸出。在這里,子選擇器是 <app-input-output>,其中 itemdeleteRequest 是子組件類中的 @Input()@Output() 屬性。屬性 currentItem 和方法 crossOffItem() 都位于父組件類中。

要使用“盒子里的香蕉”語法 [()] 組合屬性和事件綁定,請(qǐng)參見雙向綁定。

@Input() 和 @Output() 聲明

你還可以在指令元數(shù)據(jù)的 inputsoutputs 數(shù)組中標(biāo)出這些成員,而不是使用 @Input()@Output() 裝飾器來聲明輸入和輸出,如本例所示:

Path:"src/app/in-the-metadata/in-the-metadata.component.ts"

// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
inputs: ['clearanceItem'],
outputs: ['buyEvent']
// tslint:enable: no-inputs-metadata-property no-outputs-metadata-property

固然可以在 @Directive 和 @Component 元數(shù)據(jù)中聲明 inputs 和 outputs,但最好使用 @Input() 和 @Output() 類修飾符,如下所示:

Path:"src/app/input-output/input-output.component.ts"

@Input() item: string;
@Output() deleteRequest = new EventEmitter<string>();

如果在嘗試使用輸入或輸出時(shí)收到了模板解析錯(cuò)誤,但是你知道該屬性一定存在,請(qǐng)仔細(xì)檢查你的屬性是否使用 @Input() / @Output() 進(jìn)行了注解,或者是否已在 inputs / outputs 數(shù)組中聲明了它們:

&
Uncaught Error: Template parse errors:
Can't bind to 'item' since it isn't a known property of 'app-item-detail'

為輸入和輸出指定別名

有時(shí),輸入/輸出屬性的公共名稱應(yīng)與內(nèi)部名稱不同。雖然最好的方法是避免這種情況,但 Angular 確實(shí)提供了一種解決方案。

元數(shù)據(jù)中的別名

要在元數(shù)據(jù)中為輸入和輸出指定別名,請(qǐng)使用冒號(hào)分隔(:)的字符串,其左邊是屬性名,右邊是別名:

Path:"src/app/aliasing/aliasing.component.ts" ··· // tslint:disable: no-inputs-metadata-property no-outputs-metadata-property inputs: ['input1: saveForLaterItem'], // propertyName:alias outputs: ['outputEvent1: saveForLaterEvent'] // tslint:disable: no-inputs-metadata-property no-outputs-metadata-property ···

使用 @Input() / @Output() 裝飾器指定別名

你可以通過將別名傳給 @Input() / @Output() 裝飾器來為屬性名指定別名。其內(nèi)部名稱保持不變。

Path:"src/app/aliasing/aliasing.component.ts"

@Input('wishListItem') input2: string; //  @Input(alias)
@Output('wishEvent') outputEvent2 = new EventEmitter<string>(); //  @Output(alias) propertyName = ...

模板表達(dá)式中的運(yùn)算符

Angular 模板表達(dá)式的語言是 JavaScript 語法的子集,并為特定情況添加了一些特殊的運(yùn)算符。接下來將介紹其中的三個(gè)運(yùn)算符:

  1. 管道

在準(zhǔn)備將其用于綁定之前,表達(dá)式的結(jié)果可能需要進(jìn)行一些轉(zhuǎn)換。例如,你可以將數(shù)字顯示為貨幣,將文本更改為大寫,或過濾列表并對(duì)其進(jìn)行排序。

管道是簡(jiǎn)單的函數(shù),它們接受輸入值并返回轉(zhuǎn)換后的值。使用管道運(yùn)算符(|),很容易在模板表達(dá)式中使用它們:

Path:"src/app/app.component.html"

    <p>Title through uppercase pipe: {{title | uppercase}}</p>

管道運(yùn)算符會(huì)把它左側(cè)的表達(dá)式結(jié)果傳給它右側(cè)的管道函數(shù)。

還可以通過多個(gè)管道串聯(lián)表達(dá)式:

Path:"src/app/app.component.html"

    <!-- convert title to uppercase, then to lowercase -->
    <p>Title through a pipe chain: {{title | uppercase | lowercase}}</p>

你還可以對(duì)管道使用參數(shù):

Path:"src/app/app.component.html"

    <!-- pipe with configuration argument => "February 25, 1980" -->
    <p>Manufacture date with date format pipe: {{item.manufactureDate | date:'longDate'}}</p>

json 管道對(duì)調(diào)試綁定特別有用:

Path:"src/app/app.component.html"

    <p>Item json pipe: {{item | json}}</p>

生成的輸出如下所示:

    { "name": "Telephone",
      "manufactureDate": "1980-02-25T05:00:00.000Z",
      "price": 98 }

管道運(yùn)算符的優(yōu)先級(jí)比三元運(yùn)算符( ?: )高,這意味著 a ? b : c | x 將被解析為 a ? b : (c | x)。但是,由于多種原因,如果在 ?: 的第一和第二操作數(shù)中沒有括號(hào),則不能使用管道運(yùn)算符。一個(gè)較好的做法是在第三個(gè)操作數(shù)中也使用括號(hào)。

  1. 安全導(dǎo)航運(yùn)算符

Angular 安全導(dǎo)航運(yùn)算符 ? 可以對(duì)在屬性路徑中出現(xiàn) nullundefined 值進(jìn)行保護(hù)。在這里,如果 itemnull,它可以防止視圖渲染失敗。

Path:"src/app/app.component.html"

    <p>The item name is: {{item?.name}}</p>

如果 itemnull,則視圖仍然渲染,但顯示的值為空白;你只會(huì)看到 “The item name is:”,后面沒有任何內(nèi)容。

考慮接下來這個(gè)帶有 nullItem 的例子。

    The null item name is {{nullItem.name}}

由于沒有安全導(dǎo)航運(yùn)算符,并且 nullItem 為 null,因此 JavaScript 和 Angular 會(huì)引發(fā)空指針錯(cuò)誤并中斷 Angular 的渲染過程:

    TypeError: Cannot read property 'name' of null.

但是,有時(shí)在某些情況下,屬性路徑中的 null 值可能是可接受的,尤其是當(dāng)該值開始時(shí)為空但數(shù)據(jù)最終會(huì)到達(dá)時(shí)。

使用安全導(dǎo)航運(yùn)算符 ?,當(dāng) Angular 表達(dá)式遇到第一個(gè)空值時(shí),它將停止對(duì)表達(dá)式的求值,并渲染出無錯(cuò)誤的視圖。

在像 a?.b?.c?.d 這樣的長(zhǎng)屬性路徑中,它工作得很完美。

  1. 非空斷言運(yùn)算符

在 TypeScript 2.0 中,你可以使用 --strictNullChecks 標(biāo)志強(qiáng)制開啟嚴(yán)格空值檢查。TypeScript 就會(huì)確保不存在意料之外的 nullundefined。

在這種模式下,有類型的變量默認(rèn)是不允許 nullundefined 值的,如果有未賦值的變量,或者試圖把 nullundefined 賦值給不允許為空的變量,類型檢查器就會(huì)拋出一個(gè)錯(cuò)誤。

如果無法在運(yùn)行類型檢查器期間確定變量是否 nullundefined,則會(huì)拋出錯(cuò)誤。你可以通過應(yīng)用后綴非空斷言運(yùn)算符!來告訴類型檢查器不要拋出錯(cuò)誤。

Angular 的非空斷言運(yùn)算符 ! 在 Angular 模板中具有相同的目的。例如,在使用 *ngIf 檢查過 item 是否已定義之后,就可以斷言 item 屬性也已定義。

Path:"src/app/app.component.html"

    <!-- Assert color is defined, even if according to the `Item` type it could be undefined. -->
    <p>The item's color is: {{item.color!.toUpperCase()}}</p>

當(dāng) Angular 編譯器把你的模板轉(zhuǎn)換成 TypeScript 代碼時(shí),它會(huì)防止 TypeScript 不要報(bào)告此 item.color 可能為 nullundefined 的錯(cuò)誤。

與安全導(dǎo)航運(yùn)算符不同的是,非空斷言運(yùn)算符不會(huì)防止出現(xiàn) nullundefined。 它只是告訴 TypeScript 的類型檢查器對(duì)特定的屬性表達(dá)式,不做 "嚴(yán)格空值檢測(cè)"。

非空斷言運(yùn)算符 !,是可選的,但在打開嚴(yán)格空檢查選項(xiàng)時(shí)必須使用它。

內(nèi)置模板函數(shù)

類型轉(zhuǎn)換函數(shù) $any()

有時(shí)候,綁定表達(dá)式可能會(huì)在 AOT 編譯時(shí)報(bào)類型錯(cuò)誤,并且它不能或很難指定類型。要消除這種報(bào)錯(cuò),你可以使用 $any() 轉(zhuǎn)換函數(shù)來把表達(dá)式轉(zhuǎn)換成 any 類型,范例如下:

Path:"src/app/app.component.html"

<p>The item's undeclared best by date is: {{$any(item).bestByDate}}</p>

當(dāng) Angular 編譯器把模板轉(zhuǎn)換成 TypeScript 代碼時(shí),$any 表達(dá)式可以防止 TypeScript 編譯器在進(jìn)行類型檢查時(shí)報(bào)錯(cuò)說 bestByDate 不是 item 對(duì)象的成員。

$any() 轉(zhuǎn)換函數(shù)可以和 this 聯(lián)合使用,以便訪問組件中未聲明過的成員。

Path:"src/app/app.component.html"

<p>The item's undeclared best by date is: {{$any(this).bestByDate}}</p>

$any() 轉(zhuǎn)換函數(shù)可以用在綁定表達(dá)式中任何可以進(jìn)行方法調(diào)用的地方。

模板中的 SVG

可以將 SVG 用作 Angular 中的有效模板。以下所有模板語法均適用于 SVG 和 HTML。在 SVG 1.1和2.0 規(guī)范中了解更多信息。

為什么要用 SVG 作為模板,而不是簡(jiǎn)單地將其作為圖像添加到應(yīng)用程序中?

當(dāng)你使用 SVG 作為模板時(shí),就可以像 HTML 模板一樣使用指令和綁定。這意味著你將能夠動(dòng)態(tài)生成交互式圖形。

有關(guān)語法示例,請(qǐng)參見下面的示例代碼片段:

Path:"src/app/svg.component.ts"

import { Component } from '@angular/core';


@Component({
  selector: 'app-svg',
  templateUrl: './svg.component.svg',
  styleUrls: ['./svg.component.css']
})
export class SvgComponent {
  fillColor = 'rgb(255, 0, 0)';


  changeColor() {
    const r = Math.floor(Math.random() * 256);
    const g = Math.floor(Math.random() * 256);
    const b = Math.floor(Math.random() * 256);
    this.fillColor = `rgb(${r}, ${g}, $)`;
  }
}

將以下代碼添加到你的 svg.component.svg 文件中:

Path:"src/app/svg.component.svg"

<svg>
  <g>
    <rect x="0" y="0" width="100" height="100" [attr.fill]="fillColor" (click)="changeColor()" />
    <text x="120" y="50">click the rectangle to change the fill color</text>
  </g>
</svg>

在這里,你可以看到事件綁定語法 click() 和屬性綁定語法([attr.fill]="fillColor")的用法。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)