這是一篇關(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 是 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 可以有多豐富。
插值能讓你把計(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ì)算 title
和 itemImageUrl
屬性并填充空白,首先顯示一些標(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á)式會(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
、typeof
、instanceof
等運(yùn)算符。;
或 ,
串聯(lián)起來的表達(dá)式。++
和 --
。和 JavaScript 語法的其它顯著差異包括:
|
和 &
。|
,?
. 和 !
。
典型的表達(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á)式不能引用全局命名空間中的任何東西,比如
window
或document
。它們也不能調(diào)用console.log
或Math.max
。 它們只能引用表達(dá)式上下文中的成員。
當(dāng)使用模板表達(dá)式時(shí),請(qǐng)遵循下列要素:
雖然也可以寫復(fù)雜的模板表達(dá)式,不過最好避免那樣做。
屬性名或方法調(diào)用應(yīng)該是常態(tài),但偶然使用邏輯取反 ! 也是可以的。 其它情況下,應(yīng)該把應(yīng)用程序和業(yè)務(wù)邏輯限制在組件中,這樣它才能更容易開發(fā)和測(cè)試。
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ì)算得出的值。
模板表達(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
屬性。
模板語句不能引用全局命名空間的任何東西。比如不能引用 window
或 document
,也不能調(diào)用 console.log
或 Math.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ù)流的方向分為:
{{expression}}
[target]="expression"
bind-target="expression"
(target)="statement"
on-target="statement"
[(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)訪問指令的成員。
在正常的 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 屬性和 DOM 屬性之間的區(qū)別,是了解 Angular 綁定如何工作的關(guān)鍵。Attribute
是由 HTML 定義的。Property
是從 DOM(文檔對(duì)象模型)節(jié)點(diǎn)訪問的。
Attribute
可以 1:1 映射到 Property
;例如,“ id”。Attribute
沒有相應(yīng)的 Property
。例如,aria-*。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ī)則可以幫助你建立 HTMLAttribute
和 DOMProperty
的思維模型: 屬性負(fù)責(zé)初始化 DOM 屬性,然后完工。Property
值可以改變;Attribute
值則不能。
- 此規(guī)則有一個(gè)例外。 可以通過
setAttribute()
來更改Attribute
,接著它會(huì)重新初始化相應(yīng)的 DOM 屬性。
當(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)前值。
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è)布爾值),語法更短,并且性能更高。
數(shù)據(jù)綁定的目標(biāo)是 DOM 中的對(duì)象。 根據(jù)綁定類型,該目標(biāo)可以是 Property 名(元素、組件或指令的)、事件名(元素、組件或指令的),有時(shí)是 Attribute 名。下表中總結(jié)了不同綁定類型的目標(biāo)。
<img [src]="heroImageUrl">
<app-hero-detail [hero]="currentHero"></app-hero-detail>
<div [ngClass]="{'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 [(ngModel)]="name">
<button [attr.aria-label]="help">help</button>
<div [class.special]="isSpecial">Special</div>
<button [style.color]="isSpecial ? 'red' : 'green'">
使用 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>
包裹在方括號(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
/innerHTML
和tabindex
/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ì)象,依此類推。
在下面的例子中,temDetailComponent
的 childItem
屬性需要一個(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';
前面的簡(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) []
告訴 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):
你通常會(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í),必須使用屬性綁定。
假設(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.
模板語法為那些不太適合使用屬性綁定的場(chǎng)景提供了專門的單向數(shù)據(jù)綁定形式。
要在運(yùn)行中的應(yīng)用查看 Attribute 綁定、類綁定和樣式綁定,請(qǐng)參見 現(xiàn)場(chǎng)演練 / 下載范例 特別是對(duì)于本節(jié)。
可以直接使用 Attribute
綁定設(shè)置 Attribute
的值。一般來說,綁定時(shí)設(shè)置的是目標(biāo)的 Property
,而 Attribute
綁定是唯一的例外,它創(chuàng)建和設(shè)置的是 Attribute
。
通常,使用 Property
綁定設(shè)置元素的 Property
優(yōu)于使用字符串設(shè)置 Attribute
。但是,有時(shí)沒有要綁定的元素的 Property
,所以其解決方案就是 Attribute
綁定。
考慮 ARIA
和 SVG
。它們都純粹是 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>
colspan
和colSpan
注意colspan Attribute
和colSpan 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á)式中(如 object
、Array
、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
,Array
,Map
,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è)版本中刪除。
一個(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ī)則(從高到低)
- 模板綁定
- 屬性綁定(例如 <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"& )
- 指令宿主綁定
- 屬性綁定(例如,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'} )
- 組件宿主綁定
- 屬性綁定(例如,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í)的樣式可以使用 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ì)被完全刪除。
事件綁定允許你監(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)就是此按鈕的單擊事件。
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ò)誤。
在事件綁定中,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ì)象,它有像 target
和 target
.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
具體是什么由指令決定。
通常,指令使用 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
屬性,它是 EventEmitte
r 實(shí)例。 當(dāng)用戶點(diǎn)擊刪除時(shí),組件會(huì)調(diào)用 delete()
方法,讓 EventEmitter
發(fā)出一個(gè) Item
對(duì)象。
現(xiàn)在,假設(shè)有個(gè)宿主的父組件,它綁定了 ItemDetailComponent
的 deleteRequest
事件。
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ù)的方式。
雙向綁定會(huì)做兩件事:
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
事件的命名模式。
Angular 提供了兩種內(nèi)置指令:屬性型指令和結(jié)構(gòu)型指令。
屬性型指令會(huì)監(jiān)聽并修改其它 HTML 元素和組件的行為、Attribute
和 Property
。 它們通常被應(yīng)用在元素上,就好像它們是 HTML 屬性一樣,因此得名屬性型指令。
許多 NgModule(例如 RouterModule
和 FormsModule
)都定義了自己的屬性型指令。最常見的屬性型指令如下:
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()
。
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()
。
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
并將其添加到 NgModule
的 imports
列表中。要了解關(guān)于 FormsModule
和 ngModel
的更多信息,參見表單一章。
記住,要導(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)換的版本:
結(jié)構(gòu)型指令的職責(zé)是 HTML 布局。 它們塑造或重塑 DOM 的結(jié)構(gòu),這通常是通過添加、移除和操縱它們所附加到的宿主元素來實(shí)現(xiàn)的。
常見的內(nèi)置結(jié)構(gòu)型指令:
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
工作方式有所不同。如果 NgIf
為 false
,則 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>
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。
NgSwitch
—— 一組在備用視圖之間切換的指令。
NgSwitch
類似于 JavaScript switch 語句。它根據(jù)切換條件顯示幾個(gè)可能的元素中的一個(gè)。Angular 只會(huì)將選定的元素放入 DOM。
NgSwitch
實(shí)際上是三個(gè)協(xié)作指令的集合: NgSwitch
,NgSwitchCase
和 NgSwitchDefault
,如以下示例所示。
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
、NgSwitchCase
和 NgSwitchDefault
指令都是結(jié)構(gòu)型指令,因?yàn)樗鼈儠?huì)從 DOM 中添加或移除元素。
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)型指令。
模板引用變量通常是對(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()
裝飾器,可以讓 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,boolean
或 object
。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()
裝飾器,允許數(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)入 Output
和 EventEmitter
:
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
類型。該類型也可以是任何類型,例如 number
,boolean
等。有關(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()
:
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>
,其中 item
和 deleteRequest
是子組件類中的 @Input()
和 @Output()
屬性。屬性 currentItem
和方法 crossOffItem()
都位于父組件類中。
要使用“盒子里的香蕉”語法 [()]
組合屬性和事件綁定,請(qǐng)參見雙向綁定。
你還可以在指令元數(shù)據(jù)的 inputs
和 outputs
數(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ù)中為輸入和輸出指定別名,請(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()
裝飾器來為屬性名指定別名。其內(nèi)部名稱保持不變。
Path:"src/app/aliasing/aliasing.component.ts"
@Input('wishListItem') input2: string; // @Input(alias)
@Output('wishEvent') outputEvent2 = new EventEmitter<string>(); // @Output(alias) propertyName = ...
Angular 模板表達(dá)式的語言是 JavaScript 語法的子集,并為特定情況添加了一些特殊的運(yùn)算符。接下來將介紹其中的三個(gè)運(yùn)算符:
在準(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)。
Angular 安全導(dǎo)航運(yùn)算符 ? 可以對(duì)在屬性路徑中出現(xiàn) null
和 undefined
值進(jìn)行保護(hù)。在這里,如果 item
為 null
,它可以防止視圖渲染失敗。
Path:"src/app/app.component.html"
<p>The item name is: {{item?.name}}</p>
如果 item
為 null
,則視圖仍然渲染,但顯示的值為空白;你只會(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)屬性路徑中,它工作得很完美。
在 TypeScript 2.0 中,你可以使用 --strictNullChecks
標(biāo)志強(qiáng)制開啟嚴(yán)格空值檢查。TypeScript 就會(huì)確保不存在意料之外的 null
或 undefined
。
在這種模式下,有類型的變量默認(rèn)是不允許 null
或 undefined
值的,如果有未賦值的變量,或者試圖把 null
或 undefined
賦值給不允許為空的變量,類型檢查器就會(huì)拋出一個(gè)錯(cuò)誤。
如果無法在運(yùn)行類型檢查器期間確定變量是否 null
或 undefined
,則會(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
可能為 null
或 undefined
的錯(cuò)誤。
與安全導(dǎo)航運(yùn)算符不同的是,非空斷言運(yùn)算符不會(huì)防止出現(xiàn) null
或 undefined
。 它只是告訴 TypeScript 的類型檢查器對(duì)特定的屬性表達(dá)式,不做 "嚴(yán)格空值檢測(cè)"。
非空斷言運(yùn)算符 !
,是可選的,但在打開嚴(yán)格空檢查選項(xiàng)時(shí)必須使用它。
有時(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 用作 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")
的用法。
更多建議: