angular2的模板语法
Angular 應(yīng)用管理著用戶之所見和所為,并通過 Component 類的實(shí)例(組件)和面向用戶的模板來與用戶交互。
從使用模型-視圖-控制器 (MVC) 或模型-視圖-視圖模型 (MVVM) 的經(jīng)驗(yàn)中,很多開發(fā)人員都熟悉了組件和模板這兩個(gè)概念。 在 Angular 中,組件扮演著控制器或視圖模型的角色,模板則扮演視圖的角色。
來看看寫視圖的模板都需要什么。本章將覆蓋模板語法中的下列基本元素
HTML 是 Angular 模板的語言。快速起步應(yīng)用的模板是純 HTML 的:
<h1>Hello Angular</h1>幾乎所有的 HTML 語法都是有效的模板語法。但值得注意的例外是<script>元素,它被禁用了,以阻止腳本注入攻擊的風(fēng)險(xiǎn)。(實(shí)際上,<script>只是被忽略了。)
有些合法的 HTML 被用在模板中是沒有意義的。<html>、<body>和<base>元素這個(gè)舞臺(tái)上中并沒有扮演有用的角色。基本上所有其它的元素都被一樣使用。
可以通過組件和指令來擴(kuò)展模板中的 HTML 詞匯。它們看上去就是新元素和屬性。接下來將學(xué)習(xí)如何通過數(shù)據(jù)綁定來動(dòng)態(tài)獲取/設(shè)置 DOM(文檔對(duì)象模型)的值。
數(shù)據(jù)綁定的第一種形式 —— 插值表達(dá)式 —— 展示了模板的 HTML 可以有多豐富。
插值表達(dá)式
在以前的 Angular 教程中,我們遇到過由雙花括號(hào)括起來的插值表達(dá)式,{{和}}。
<p>My current hero is {{currentHero.firstName}}</p>插值表達(dá)式可以把計(jì)算后的字符串插入到 HTML 元素標(biāo)簽內(nèi)的文本或?qū)?biāo)簽的屬性進(jìn)行賦值。
<h3>{{title}}<img src="{{heroImageUrl}}" style="height:30px"> </h3>在括號(hào)之間的“素材”,通常是組件屬性的名字。Angular 會(huì)用組件中相應(yīng)屬性的字符串值,替換這個(gè)名字。 上例中,Angular 計(jì)算title和heroImageUrl屬性的值,并把它們填在空白處。 首先顯示粗體的應(yīng)用標(biāo)題,然后顯示英雄的圖片。
一般來說,括號(hào)間的素材是一個(gè)模板表達(dá)式,Angular 先對(duì)它求值,再把它轉(zhuǎn)換成字符串。 下列插值表達(dá)式通過把括號(hào)中的兩個(gè)數(shù)字相加說明
<!-- "The sum of 1 + 1 is 2" --> <p>The sum of 1 + 1 is {{1 + 1}}</p>這個(gè)表達(dá)式可以調(diào)用宿主組件的方法,就像下面用的getVal():
<!-- "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é)果賦給元素或指令的屬性。
表面上看,我們?cè)谠貥?biāo)簽之間插入了結(jié)果和對(duì)標(biāo)簽的屬性進(jìn)行了賦值。 這樣思考起來很方便,并且這個(gè)誤解很少給我們帶來麻煩。 但嚴(yán)格來講,這是不對(duì)的。插值表達(dá)式是一個(gè)特殊的語法,Angular 把它轉(zhuǎn)換成了屬性綁定,后面將會(huì)解釋這一點(diǎn)。
講解屬性綁定之前,先深入了解一下模板表達(dá)式和模板語句。
模板表達(dá)式
模板表達(dá)式產(chǎn)生一個(gè)值。 Angular 執(zhí)行這個(gè)表達(dá)式,并把它賦值給綁定目標(biāo)的屬性,這個(gè)綁定目標(biāo)可能是 HTML 元素、組件或指令。
當(dāng)我們寫{{1 + 1}}時(shí),是往插值表達(dá)式的括號(hào)中放進(jìn)了一個(gè)模板表達(dá)式。 在屬性綁定中會(huì)再次看到模板表達(dá)式,它出現(xiàn)在=右側(cè)的引號(hào)中,看起來像這樣:[property]="expression"。
編寫模板表達(dá)式所用的語言看起來很像 JavaScript。 很多 JavaScript 表達(dá)式也是合法的模板表達(dá)式,但不是全部。
JavaScript 中那些具有或可能引發(fā)副作用的表達(dá)式是被禁止的,包括:
-
賦值 (=,?+=,?-=, ...)
-
new運(yùn)算符
-
使用;或,的鏈?zhǔn)奖磉_(dá)式
-
自增或自減操作符 (++和--)
和 JavaScript語 法的其它顯著不同包括:
-
不支持位運(yùn)算|和&
-
具有新的模板表達(dá)式運(yùn)算符,比如|和?.
表達(dá)式上下文
也許更讓人吃驚的是,模板表達(dá)式不能引用全局命名空間中的任何東西。 不能引用window或document。不能調(diào)用console.log或Math.max。 它們被局限于只能訪問來自表達(dá)式上下文中的成員。
典型的表達(dá)式上下文就是這個(gè)組件實(shí)例,它是各種綁定值的來源。
當(dāng)看到包裹在雙花括號(hào)中的?title?({{title}}) 時(shí),我們就知道title是這個(gè)數(shù)據(jù)綁定組件中的一個(gè)屬性。 當(dāng)看到[disabled]="isUnchanged"中的?isUnchanged?時(shí),我們就知道正在引用該組件的isUnchanged屬性。
通常,組件本身就是表達(dá)式的上下文,這種情況下,模板表達(dá)式會(huì)引用那個(gè)組件。
表達(dá)式的上下文可以包括組件之外的對(duì)象。?模板引用變量就是備選的上下文對(duì)象之一。
?
表達(dá)式指南
模板表達(dá)式能成就或毀掉一個(gè)應(yīng)用。請(qǐng)遵循下列指南:
-
沒有可見的副作用
-
執(zhí)行迅速
-
非常簡單
-
冪等性
超出上面指南外的情況應(yīng)該只出現(xiàn)在那些你確信自己已經(jīng)徹底理解的特定場(chǎng)景中。
沒有可見的副作用
模板表達(dá)式除了目標(biāo)屬性的值以外,不應(yīng)該改變應(yīng)用的任何狀態(tài)。
這條規(guī)則是 Angular “單向數(shù)據(jù)流”策略的基礎(chǔ)。 永遠(yuǎn)不用擔(dān)心讀取組件值可能改變另外的顯示值。 在一次單獨(dú)的渲染過程中,視圖應(yīng)該總是穩(wěn)定的。
執(zhí)行迅速
Angular 執(zhí)行模板表達(dá)式比我們想象的頻繁。 它們可能在每一次按鍵或鼠標(biāo)移動(dòng)后被調(diào)用。 表達(dá)式應(yīng)該快速結(jié)束,否則用戶就會(huì)感到拖沓,特別是在較慢的設(shè)備上。 當(dāng)計(jì)算代價(jià)較高時(shí),應(yīng)該考慮緩存那些從其它值計(jì)算得出的值。
非常簡單
雖然可以寫出相當(dāng)復(fù)雜的模板表達(dá)式,但不要那么去寫。
常規(guī)是屬性名或方法調(diào)用。偶爾的邏輯取反 (!) 也還湊合。 其它情況下,應(yīng)在組件中實(shí)現(xiàn)應(yīng)用和業(yè)務(wù)邏輯,使開發(fā)和測(cè)試變得更容易。
冪等性
最好使用冪等的表達(dá)式,因?yàn)樗鼪]有副作用,并且能提升 Angular 變更檢測(cè)的性能。
在 Angular 的術(shù)語中,冪等的表達(dá)式應(yīng)該總是返回完全相同的東西,直到某個(gè)依賴值發(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ì)象的引用。
模板語句
模板語句用來響應(yīng)由綁定目標(biāo)(如 HTML 元素、組件或指令)觸發(fā)的事件。
模板語句將在事件綁定一節(jié)看到,它出現(xiàn)在=號(hào)右側(cè)的引號(hào)中,就像這樣:(event)="statement"。
模板語句有副作用。 這正是用戶輸入更新應(yīng)用狀態(tài)的方式。 否則,響應(yīng)事件就沒有什么意義了。
響應(yīng)事件是 Angular 中“單向數(shù)據(jù)流”的另一面。 在一次事件循環(huán)中,可以隨意改變?nèi)魏蔚胤降娜魏螙|西。
和模板表達(dá)式一樣,模板語句使用的語言也像 JavaScript。 模板語句解析器和模板表達(dá)式解析器有所不同,特別之處在于它支持基本賦值 (=) 和表達(dá)式鏈 (;和,)。
然而,某些 JavaScript 語法仍然是不允許的:
-
new運(yùn)算符
-
自增和自減運(yùn)算符:++和--
-
操作并賦值,例如+=和-=
-
位操作符|和&
-
模板表達(dá)式運(yùn)算符
語句上下文
和表達(dá)式中一樣,語句只能引用語句上下文中 —— 通常是正在綁定事件的那個(gè)組件實(shí)例。
模板語句無法引用全局命名空間的任何東西。它們不能引用window或者document, 不能調(diào)用console.log或者M(jìn)ath.max。
(click)="onSave()"中的?onSave?就是數(shù)據(jù)綁定組件實(shí)例中的方法。
語句上下文可以包含組件之外的對(duì)象。?模板引用對(duì)象就是備選上下文對(duì)象之一。 在事件綁定語句中,經(jīng)常會(huì)看到被保留的$event符號(hào),它代表觸發(fā)事件的“消息”或“有效載荷”。
語句指南
和表達(dá)式一樣,避免寫復(fù)雜的模板語句。 常規(guī)是函數(shù)調(diào)用或者屬性賦值。
現(xiàn)在,對(duì)模板表達(dá)式和語句有了一點(diǎn)感覺了吧。 除插值表達(dá)式外,還有各種各樣的數(shù)據(jù)綁定語法,是學(xué)習(xí)它們是時(shí)候了。
?
綁定語法:概覽
數(shù)據(jù)綁定是一種機(jī)制,用來協(xié)調(diào)用戶所見和應(yīng)用數(shù)據(jù)。 雖然我們能往 HTML 推送值或者從 HTML 拉取值, 但如果把這些瑣事交給數(shù)據(jù)綁定框架處理, 應(yīng)用會(huì)更容易編寫、閱讀和維護(hù)。 只要簡單地在綁定源和目標(biāo) HTML 元素之間聲明綁定,框架就會(huì)完成這項(xiàng)工作。
Angular 提供了各種各樣的數(shù)據(jù)綁定,本章將逐一討論。 首先,從高層視角來看看 Angular 數(shù)據(jù)綁定和它的語法。
根據(jù)數(shù)據(jù)流的方向,可以把所有綁定歸為三類。 每一類都有它獨(dú)特的語法:
| 單向 從數(shù)據(jù)源 到視圖目標(biāo) | COPY CODE {{expression}} [target] = "expression" bind-target = "expression" | 插值表達(dá)式 Property Attribute 類 樣式 |
| 單向 從視圖目標(biāo) 到數(shù)據(jù)源 | COPY CODE (target) = "statement" on-target = "statement" | 事件 |
| 雙向 | COPY CODE [(target)] = "expression" bindon-target = "expression" | 雙向 |
譯注:由于 HTML attribute 和 DOM property 在中文中都被翻譯成了“屬性”,無法區(qū)分, 而接下來的部分重點(diǎn)是對(duì)它們進(jìn)行比較。
我們無法改變歷史,因此,在本章的翻譯中,保留了它們的英文形式,不加翻譯,以免混淆。 本章中,如果提到“屬性”的地方,一定是指 property,因?yàn)樵?Angular 中,實(shí)際上很少涉及 attribute。
但在其它章節(jié)中,為簡單起見,凡是能通過上下文明顯區(qū)分開的,就仍統(tǒng)一譯為“屬性”, 區(qū)分不明顯的,會(huì)加注英文。
除了插值表達(dá)式之外的綁定類型,在等號(hào)左邊是目標(biāo)名, 無論是包在括號(hào)中 ([]、()) 還是用前綴形式 (bind-、on-、bindon-) 。
什么是“目標(biāo)”?在回答這個(gè)問題之前,我們必須先挑戰(zhàn)下自我,嘗試用另一種方式來審視模板中的 HTML。
新的思維模型
數(shù)據(jù)綁定的威力和允許用自定義標(biāo)記擴(kuò)展 HTML 詞匯的能力,容易誤導(dǎo)我們把模板 HTML 當(dāng)成?HTML+。
也對(duì),它是?HTML+。 但它也跟我們熟悉的 HTML 有著顯著的不同。 我們需要一種新的思維模型。
在正常的 HTML 開發(fā)過程中,我們使用 HTML 元素創(chuàng)建視覺結(jié)構(gòu), 通過把字符串常量設(shè)置到元素的 attribute 來修改那些元素。
<div class="special">Mental Model</div> <img src="images/hero.png"> <button disabled>Save</button>在 Angular 模板中,我們?nèi)允褂猛瑯拥姆绞絹韯?chuàng)建結(jié)構(gòu)和初始化 attribute 值。
然后,用封裝了 HTML 的組件創(chuàng)建新元素,并把它們當(dāng)作原生 HTML 元素在模板中使用。
<!-- Normal HTML --> <div class="special">Mental Model</div> <!-- Wow! A new element! --> <hero-detail></hero-detail>這就是HTML+。
現(xiàn)在開始學(xué)習(xí)數(shù)據(jù)綁定。我們碰到的第一種數(shù)據(jù)綁定看起來是這樣的:
<!-- Bind button disabled state to `isUnchanged` property --> <button [disabled]="isUnchanged">Save</button>過會(huì)兒再認(rèn)識(shí)那個(gè)怪異的方括號(hào)記法。直覺告訴我們,我們正在綁定按鈕的disabled?attribute。 并把它設(shè)置為組件的isUnchanged屬性的當(dāng)前值。
但我們的直覺是錯(cuò)的!日常的 HTML 思維模式在誤導(dǎo)我們。 實(shí)際上,一旦開始數(shù)據(jù)綁定,就不再跟 HTML attribute 打交道了。 這里不是設(shè)置 attribute,而是設(shè)置 DOM 元素、組件和指令的 property。
HTML attribute 與 DOM property 的對(duì)比
要想理解 Angular 綁定如何工作,重點(diǎn)是搞清 HTML attribute 和 DOM property 之間的區(qū)別。
attribute 是由 HTML 定義的。property 是由 DOM (Document Object Model) 定義的。
-
少量 HTML attribute 和 property 之間有著 1:1 的映射,如id。
-
有些 HTML attribute 沒有對(duì)應(yīng)的 property,如colspan。
-
有些 DOM property 沒有對(duì)應(yīng)的 attribute,如textContent。
-
大量 HTML attribute看起來映射到了property…… 但卻不像我們想的那樣!
最后一類尤其讓人困惑…… 除非我們能理解這個(gè)普遍原則:
attribute?初始化?DOM property,然后它們的任務(wù)就完成了。property 的值可以改變;attribute 的值不能改變。
例如,當(dāng)瀏覽器渲染<input type="text" value="Bob">時(shí),它將創(chuàng)建相應(yīng) DOM 節(jié)點(diǎn), 其value?property 被初始化為?“Bob”。
當(dāng)用戶在輸入框中輸入 “Sally” 時(shí),DOM 元素的value?property?變成了 “Sally”。 但是這個(gè) HTML?value?attribute?保持不變。如果我們讀取 input 元素的 attribute,就會(huì)發(fā)現(xiàn)確實(shí)沒變:?input.getAttribute('value') // 返回 "Bob"。
HTML attribute?value指定了初始值;DOM?value?property 是當(dāng)前值。
disabled?attribute 是另一個(gè)古怪的例子。按鈕的disabled?property?是false,因?yàn)槟J(rèn)情況下按鈕是可用的。 當(dāng)我們添加disabledattribute?時(shí),只要它出現(xiàn)了按鈕的disabled?property?就初始化為true,于是按鈕就被禁用了。
添加或刪除disabled?attribute會(huì)禁用或啟用這個(gè)按鈕。但?attribute?的值無關(guān)緊要,這就是我們?yōu)槭裁礇]法通過?<button disabled="false">仍被禁用</button>這種寫法來啟用按鈕。
設(shè)置按鈕的disabled?property(如,通過 Angular 綁定)可以禁用或啟用這個(gè)按鈕。 這就是?property?的價(jià)值。
就算名字相同,HTML attribute 和 DOM property 也不是同一樣?xùn)|西。
這句話很重要,得再強(qiáng)調(diào)一次:
模板綁定是通過?property?和事件來工作的,而不是?attribute。
沒有 ATTRIBUTE 的世界在 Angular 的世界中,attribute 唯一的作用是用來初始化元素和指令的狀態(tài)。 當(dāng)進(jìn)行數(shù)據(jù)綁定時(shí),只是在與元素和指令的 property 和事件打交道,而 attribute 就完全靠邊站了。
把這個(gè)思維模型牢牢的印在腦子里,接下來,學(xué)習(xí)什么是綁定目標(biāo)。
綁定目標(biāo)
數(shù)據(jù)綁定的目標(biāo)是 DOM 中的某些東西。 這個(gè)目標(biāo)可能是(元素 | 組件 | 指令的)property、(元素 | 組件 | 指令的)事件,或(極少數(shù)情況下) attribute 名。 下面是的匯總表:
| Property | 元素的 property 組件的 property 指令的 property | COPY CODE <img [src] = "heroImageUrl"> <hero-detail [hero]="currentHero"></hero-detail> <div [ngClass] = "{selected: isSelected}"></div> |
| 事件 | 元素的事件 組件的事件 指令的事件 | COPY CODE <button (click) = "onSave()">Save</button> <hero-detail (deleteRequest)="deleteHero()"></hero-detail> <div (myClick)="clicked=$event">click me</div> |
| 雙向 | 事件與 property | COPY CODE <input [(ngModel)]="heroName"> |
| Attribute | attribute(例外情況) | COPY CODE <button [attr.aria-label]="help">help</button> |
| CSS 類 | class?property | COPY CODE <div [class.special]="isSpecial">Special</div> |
| 樣式 | style?property | COPY CODE <button [style.color] = "isSpecial ? 'red' : 'green'"> |
讓我們從結(jié)構(gòu)性云層中走出來,看看每種綁定類型的具體情況。
屬性 (property) 綁定
當(dāng)要把視圖元素的屬性 (property) 設(shè)置為模板表達(dá)式時(shí),就要寫模板的屬性 (property) 綁定。
最常用的屬性綁定是把元素屬性設(shè)置為組件屬性的值。 下面這個(gè)例子中,image 元素的src屬性會(huì)被綁定到組件的heroImageUrl屬性上:
<img [src]="heroImageUrl">另一個(gè)例子是當(dāng)組件說它isUnchanged(未改變)時(shí)禁用按鈕:
<button [disabled]="isUnchanged">Cancel is disabled</button>另一個(gè)例子是設(shè)置指令的屬性:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>還有另一個(gè)例子是設(shè)置自定義組件的模型屬性(這是父子組件之間通訊的重要途徑):
<hero-detail [hero]="currentHero"></hero-detail>單向輸入
人們經(jīng)常把屬性綁定描述成單向數(shù)據(jù)綁定,因?yàn)橹档牧鲃?dòng)是單向的,從組件的數(shù)據(jù)屬性流動(dòng)到目標(biāo)元素的屬性。
不能使用屬性綁定來從目標(biāo)元素拉取值,也不能綁定到目標(biāo)元素的屬性來讀取它。只能設(shè)置它。
也不能使用屬性 綁定 來調(diào)用目標(biāo)元素上的方法。
如果這個(gè)元素觸發(fā)了事件,可以通過事件綁定來監(jiān)聽它們。
如果必須讀取目標(biāo)元素上的屬性或調(diào)用它的某個(gè)方法,得用另一種技術(shù)。 參見 API 參考手冊(cè)中的?ViewChild?和?ContentChild。
綁定目標(biāo)
包裹在方括號(hào)中的元素屬性名標(biāo)記著目標(biāo)屬性。下列代碼中的目標(biāo)屬性是 image 元素的src屬性。
<img [src]="heroImageUrl">有些人喜歡用bind-前綴的可選形式,并稱之為規(guī)范形式:
<img bind-src="heroImageUrl">目標(biāo)的名字總是 property 的名字。即使它看起來和別的名字一樣。 看到src時(shí),可能會(huì)把它當(dāng)做 attribute。不!它不是!它是 image 元素的 property 名。
元素屬性可能是最常見的綁定目標(biāo),但 Angular 會(huì)先去看這個(gè)名字是否是某個(gè)已知指令的屬性名,就像下面的例子中一樣:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>嚴(yán)格來說,Angular 正在匹配指令的輸入屬性的名字。 這個(gè)名字是指令的inputs數(shù)組中所列的名字,或者是帶有@Input()裝飾器的屬性。 這些輸入屬性被映射為指令自己的屬性。
如果名字沒有匹配上已知指令或元素的屬性,Angular 就會(huì)報(bào)告“未知指令”的錯(cuò)誤。
返回恰當(dāng)?shù)念愋?/h3>
模板表達(dá)式應(yīng)該返回目標(biāo)屬性所需類型的值。 如果目標(biāo)屬性想要個(gè)字符串,就返回字符串。 如果目標(biāo)屬性想要個(gè)數(shù)字,就返回?cái)?shù)字。 如果目標(biāo)屬性想要個(gè)對(duì)象,就返回對(duì)象。
HeroDetail組件的hero屬性想要一個(gè)Hero對(duì)象,那就在屬性綁定中精確地給它一個(gè)Hero對(duì)象:
<hero-detail [hero]="currentHero"></hero-detail>別忘了方括號(hào)
方括號(hào)告訴 Angular 要計(jì)算模板表達(dá)式。 如果忘了加方括號(hào),Angular 會(huì)把這個(gè)表達(dá)式當(dāng)做字符串常量看待,并用該字符串來初始化目標(biāo)屬性。 它不會(huì)計(jì)算這個(gè)字符串。
不要出現(xiàn)這樣的失誤:
<!-- ERROR: HeroDetailComponent.hero expects aHero object, not the string "currentHero" --><hero-detail hero="currentHero"></hero-detail>一次性字符串初始化
當(dāng)下列條件滿足時(shí),應(yīng)該省略括號(hào):
-
目標(biāo)屬性接受字符串值。
-
字符串是個(gè)固定值,可以直接合并到模塊中。
-
這個(gè)初始值永不改變。
我們經(jīng)常這樣在標(biāo)準(zhǔn) HTML 中用這種方式初始化 attribute,這種方式也可以用在初始化指令和組件的屬性。 下面這個(gè)例子把HeroDetailComponent的prefix屬性初始化為固定的字符串,而不是模板表達(dá)式。Angular 設(shè)置它,然后忘記它。
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>作為對(duì)比,[hero]綁定是組件的currentHero屬性的活綁定,它會(huì)一直隨著更新。
屬性綁定還是插值表達(dá)式?
我們通常得在插值表達(dá)式和屬性綁定之間做出選擇。 下列這幾對(duì)綁定做的事情完全相同:
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p> <p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p> <p><span>"{{title}}" is the <i>interpolated</i> title.</span></p> <p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>在多數(shù)情況下,插值表達(dá)式是更方便的備選項(xiàng)。 實(shí)際上,在渲染視圖之前,Angular 把這些插值表達(dá)式翻譯成相應(yīng)的屬性綁定。
當(dāng)要渲染的數(shù)據(jù)類型是字符串時(shí),沒有技術(shù)上的理由證明哪種形式更好。 我們傾向于可讀性,所以傾向于插值表達(dá)式。 建議建立代碼風(fēng)格規(guī)則,選擇一種形式, 這樣,既遵循了規(guī)則,又能讓手頭的任務(wù)做起來更自然。
但數(shù)據(jù)類型不是字符串時(shí),就必須使用屬性綁定了。
內(nèi)容安全
假設(shè)下面的惡毒內(nèi)容
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';幸運(yùn)的是,Angular 數(shù)據(jù)綁定對(duì)危險(xiǎn) HTML 有防備。 在顯示它們之前,它對(duì)內(nèi)容先進(jìn)行消毒。 不管是插值表達(dá)式還是屬性綁定,都不會(huì)允許帶有 script 標(biāo)簽的 HTML 泄漏到瀏覽器中。
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p> <p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>插值表達(dá)式處理 script 標(biāo)簽與屬性綁定有所不同,但是二者都只渲染沒有危害的內(nèi)容。
?
attribute、class 和 style 綁定
模板語法為那些不太適合使用屬性綁定的場(chǎng)景提供了專門的單向數(shù)據(jù)綁定形式。
attribute 綁定
可以通過attribute 綁定來直接設(shè)置 attribute 的值。
這是“綁定到目標(biāo)屬性 (property)”這條規(guī)則中唯一的例外。這是唯一的能創(chuàng)建和設(shè)置 attribute 的綁定形式。
本章中,通篇都在說通過屬性綁定來設(shè)置元素的屬性總是好于用字符串設(shè)置 attribute。為什么 Angular 還提供了 attribute 綁定呢?
因?yàn)楫?dāng)元素沒有屬性可綁的時(shí)候,就必須使用 attribute 綁定。
考慮?ARIA,?SVG?和 table 中的 colspan/rowspan 等 attribute。 它們是純粹的 attribute,沒有對(duì)應(yīng)的屬性可供綁定。
如果想寫出類似下面這樣的東西,現(xiàn)狀會(huì)令我們痛苦:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>會(huì)得到這個(gè)錯(cuò)誤:
Template parse errors: Can't bind to 'colspan' since it isn't a known native property模板解析錯(cuò)誤:不能綁定到 'colspan',因?yàn)樗皇且阎脑鷮傩?正如提示中所說,<td>元素沒有colspan屬性。 但是插值表達(dá)式和屬性綁定只能設(shè)置屬性,不能設(shè)置 attribute。
我們需要 attribute 綁定來創(chuàng)建和綁定到這樣的 attribute。
attribute 綁定的語法與屬性綁定類似。 但方括號(hào)中的部分不是元素的屬性名,而是由attr前綴,一個(gè)點(diǎn) (.) 和 attribute 的名字組成。 可以通過值為字符串的表達(dá)式來設(shè)置 attribute 的值。
這里把[attr.colspan]綁定到一個(gè)計(jì)算值:
<table border=1> <!-- expression calculates colspan=2 --> <tr><td [attr.colspan]="1 + 1">One-Two</td></tr> <!-- ERROR: There is no `colspan` property to set! <tr><td colspan="{{1 + 1}}">Three-Four</td></tr> --> <tr><td>Five</td><td>Six</td></tr> </table>這里是表格渲染出來的樣子:
| One-Two | |
| Five | Six |
attribute 綁定的主要用例之一是設(shè)置 ARIA attribute(譯注:ARIA指可訪問性,用于給殘障人士訪問互聯(lián)網(wǎng)提供便利), 就像這個(gè)例子中一樣:
<!-- create and set an aria attribute for assistive technology --> <button [attr.aria-label]="actionName">{{actionName}} with Aria</button>CSS 類綁定
借助?CSS 類綁定,可以從元素的class?attribute 上添加和移除 CSS 類名。
CSS 類綁定綁定的語法與屬性綁定類似。 但方括號(hào)中的部分不是元素的屬性名,而是由class前綴,一個(gè)點(diǎn) (.)和 CSS 類的名字組成, 其中后兩部分是可選的。形如:[class.class-name]。
下列例子示范了如何通過 CSS 類綁定來添加和移除應(yīng)用的 "special" 類。不用綁定直接設(shè)置 attribute 時(shí)是這樣的:
<!-- standard class attribute setting --> <div class="bad curly special">Bad curly special</div>可以把它改寫為綁定到所需 CSS 類名的綁定;這是一個(gè)或者全有或者全無的替換型綁定。 (譯注:即當(dāng) badCurly 有值時(shí) class 這個(gè) attribute 設(shè)置的內(nèi)容會(huì)被完全覆蓋)
<!-- reset/override all class names with a binding --> <div class="bad curly special" [class]="badCurly">Bad curly</div>最后,可以綁定到特定的類名。 當(dāng)模板表達(dá)式的求值結(jié)果是真值時(shí),Angular 會(huì)添加這個(gè)類,反之則移除它。
<!-- toggle the "special" class on/off with a property --> <div [class.special]="isSpecial">The class binding is special</div> <!-- binding to `class.special` trumps the class attribute --> <div class="special" [class.special]="!isSpecial">This one is not so special</div>雖然這是切換單一類名的好辦法,但我們通常更喜歡使用?NgClass指令?來同時(shí)管理多個(gè)類名。
樣式綁定
通過樣式綁定,可以設(shè)置內(nèi)聯(lián)樣式。
樣式綁定的語法與屬性綁定類似。 但方括號(hào)中的部分不是元素的屬性名,而由style前綴,一個(gè)點(diǎn) (.)和 CSS 樣式的屬性名組成。 形如:[style.style-property]。
<button [style.color] = "isSpecial ? 'red': 'green'">Red</button> <button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>有些樣式綁定中的樣式帶有單位。在這里,以根據(jù)條件用 “em” 和 “%” 來設(shè)置字體大小的單位。
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button> <button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>雖然這是設(shè)置單一樣式的好辦法,但我們通常更喜歡使用?NgStyle指令?來同時(shí)設(shè)置多個(gè)內(nèi)聯(lián)樣式。
注意,樣式屬性命名方法可以用中線命名法,像上面的一樣 也可以用駝峰式命名法,如fontSize。
事件綁定
前面遇到的綁定的數(shù)據(jù)流都是單向的:從組件到元素。
用戶不會(huì)只盯著屏幕看。它們會(huì)在輸入框中輸入文本。它們會(huì)從列表中選取條目。 它們會(huì)點(diǎn)擊按鈕。這類用戶動(dòng)作可能導(dǎo)致反向的數(shù)據(jù)流:從元素到組件。
知道用戶動(dòng)作的唯一方式是監(jiān)聽某些事件,如按鍵、鼠標(biāo)移動(dòng)、點(diǎn)擊和觸摸屏幕。 可以通過 Angular 事件綁定來聲明對(duì)哪些用戶動(dòng)作感興趣。
事件綁定語法由等號(hào)左側(cè)帶圓括號(hào)的目標(biāo)事件和右側(cè)引號(hào)中的模板語句組成。 下面事件綁定監(jiān)聽按鈕的點(diǎn)擊事件。每當(dāng)點(diǎn)擊發(fā)生時(shí),都會(huì)調(diào)用組件的onSave()方法。
<button (click)="onSave()">Save</button>目標(biāo)事件
圓括號(hào)中的名稱?—— 比如(click)?—— 標(biāo)記出目標(biāo)事件。在下面例子中,目標(biāo)是按鈕的 click 事件。
<button (click)="onSave()">Save</button>有些人更喜歡帶on-前綴的備選形式,稱之為規(guī)范形式:
<button on-click="onSave()">On Save</button>元素事件可能是更常見的目標(biāo),但 Angular 會(huì)先看這個(gè)名字是否能匹配上已知指令的事件屬性,就像下面這個(gè)例子:
<!-- `myClick` is an event on the custom `ClickDirective` --> <div (myClick)="clickMessage=$event">click with myClick</div>更多關(guān)于該myClick指令的解釋,見給輸入/輸出屬性起別名。
如果這個(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ì)象,它有像target和target.value這樣的屬性。
考慮這個(gè)范例:
<input [value]="currentHero.firstName" (input)="currentHero.firstName=$event.target.value" >上面的代碼在把輸入框的value屬性綁定到firstName屬性。 要監(jiān)聽對(duì)值的修改,代碼綁定到輸入框的input事件。 當(dāng)用戶造成更改時(shí),input事件被觸發(fā),并在包含了 DOM 事件對(duì)象 ($event) 的上下文中執(zhí)行這條語句。
要更新firstName屬性,就要通過路徑$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è)HeroDetailComponent用于顯示英雄的信息,并響應(yīng)用戶的動(dòng)作。 雖然HeroDetailComponent包含刪除按鈕,但它自己并不知道該如何刪除這個(gè)英雄。 最好的做法是觸發(fā)事件來報(bào)告“刪除用戶”的請(qǐng)求。
下面的代碼節(jié)選自HeroDetailComponent:
src/app/hero-detail.component.ts (template)
template: ` <div><img src="{{heroImageUrl}}"><span [style.text-decoration]="lineThrough">{{prefix}} {{hero?.fullName}}</span><button (click)="delete()">Delete</button> </div>`src/app/hero-detail.component.ts (deleteRequest)
// This component make a request but it can't actually delete a hero. deleteRequest = new EventEmitter<Hero>(); delete() { this.deleteRequest.emit(this.hero); }組件定義了deleteRequest屬性,它是EventEmitter實(shí)例。 當(dāng)用戶點(diǎn)擊刪除時(shí),組件會(huì)調(diào)用delete()方法,讓EventEmitter發(fā)出一個(gè)Hero對(duì)象。
現(xiàn)在,假設(shè)有個(gè)宿主的父組件,它綁定了HeroDetailComponent的deleteRequest事件。
<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>當(dāng)deleteRequest事件觸發(fā)時(shí),Angular 調(diào)用父組件的deleteHero方法, 在$event變量中傳入要?jiǎng)h除的英雄(來自HeroDetail)。
模板語句有副作用
deleteHero方法有副作用:它刪除了一個(gè)英雄。 模板語句的副作用不僅沒問題,反而正是所期望的。
刪除這個(gè)英雄會(huì)更新模型,還可能觸發(fā)其它修改,包括向遠(yuǎn)端服務(wù)器的查詢和保存。 這些變更通過系統(tǒng)進(jìn)行擴(kuò)散,并最終顯示到當(dāng)前以及其它視圖中。
雙向數(shù)據(jù)綁定
我們經(jīng)常需要顯示數(shù)據(jù)屬性,并在用戶作出更改時(shí)更新該屬性。
在元素層面上,既要設(shè)置元素屬性,又要監(jiān)聽元素事件變化。
Angular 為此提供一種特殊的雙向數(shù)據(jù)綁定語法:[(x)]。?[(x)]語法結(jié)合了屬性綁定的方括號(hào)[x]和事件綁定的圓括號(hào)(x)。
[( )] = 盒子里的香蕉想象盒子里的香蕉來記住方括號(hào)套圓括號(hào)。
當(dāng)一個(gè)元素?fù)碛锌梢栽O(shè)置的屬性x和對(duì)應(yīng)的事件xChange時(shí),解釋[(x)]語法就容易多了。 下面的SizerComponent符合這個(gè)模式。它有size屬性和伴隨的sizeChange事件:
src/app/sizer.component.ts
size的初始值是一個(gè)輸入值,來自屬性綁定。(譯注:注意size前面的@Input) 點(diǎn)擊按鈕,在最小/最大值范圍限制內(nèi)增加或者減少size。 然后用調(diào)整后的size觸發(fā)sizeChange事件。
下面的例子中,AppComponent.fontSize被雙向綁定到SizerComponent:
<my-sizer [(size)]="fontSizePx"></my-sizer> <div [style.font-size.px]="fontSizePx">Resizable Text</div>SizerComponent.size初始值是AppComponent.fontSizePx。 點(diǎn)擊按鈕時(shí),通過雙向綁定更新AppComponent.fontSizePx。 被修改的AppComponent.fontSizePx通過樣式綁定,改變文本的顯示大小。 試一下在線例子。
雙向綁定語法實(shí)際上是屬性綁定和事件綁定的語法糖。 Angular將SizerComponent的綁定分解成這樣:
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>$event變量包含了SizerComponent.sizeChange事件的荷載。 當(dāng)用戶點(diǎn)擊按鈕時(shí),Angular 將$event賦值給AppComponent.fontSizePx。
很清楚,比起單獨(dú)綁定屬性和事件,雙向數(shù)據(jù)綁定語法顯得非常方便。
我們希望能在像<input>和<select>這樣的 HTML 元素上使用雙向數(shù)據(jù)綁定。 可惜,原生 HTML 元素不遵循x值和xChange事件的模式。
幸運(yùn)的是,Angular 以?NgModel?指令為橋梁,允許在表單元素上使用雙向數(shù)據(jù)綁定。
使用 NgModel 進(jìn)行雙向數(shù)據(jù)綁定
當(dāng)開發(fā)數(shù)據(jù)輸入表單時(shí),我們經(jīng)常希望能顯示數(shù)據(jù)屬性,并在用戶做出變更時(shí)更新該屬性。
使用NgModel指令進(jìn)行雙向數(shù)據(jù)綁定讓它變得更加容易。請(qǐng)看下例:
<input [(ngModel)]="currentHero.firstName"> 要使用 NGMODEL,必須導(dǎo)入 FORMSMODULE在使用ngModel做雙向數(shù)據(jù)綁定之前,得先導(dǎo)入FormsModule, 把它加入 Angular 模塊的imports列表。 學(xué)習(xí)關(guān)于FormsModule和ngModel的更多知識(shí),參見表單。
下面展示了如何導(dǎo)入FormsModule,讓[(ngModel)]變得可用:
src/app/app.module.ts (FormsModule import)
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }[(ngModel)]內(nèi)幕
回顧一下firstName的綁定,值得注意的是,可以通過分別綁定<input>元素的value屬性和`input事件來實(shí)現(xiàn)同樣的效果。
<input [value]="currentHero.firstName" (input)="currentHero.firstName=$event.target.value" >這樣很笨拙。誰能記住哪個(gè)元素屬性用于設(shè)置,哪個(gè)用于發(fā)出用戶更改? 如何從輸入框中提取出當(dāng)前顯示的文本,以便更新數(shù)據(jù)屬性? 誰想每次都去查一遍?
ngModel指令通過它自己的ngModel輸入屬性和ngModelChange輸出屬性隱藏了這些繁瑣的細(xì)節(jié)。
<input[ngModel]="currentHero.firstName" (ngModelChange)="currentHero.firstName=$event">ngModel數(shù)據(jù)屬性設(shè)置元素的 value 屬性,ngModelChange事件屬性監(jiān)聽元素 value 的變化。
每種元素的特點(diǎn)各不相同,所以NgModel指令只能在一些特定表單元素上使用,例如輸入文本框,因?yàn)樗鼈冎С?ControlValueAccessor。
除非寫一個(gè)合適的值訪問器,否則不能把[(ngModel)]用在自定義組件上。 但值訪問器技術(shù)超出了本章的范圍。 對(duì)于不能控制其 API 的 Angular 組件或者 Web 組件,可能需要為其添加?value accessor。
但是對(duì)于我們能控制的 Angular 組件來說,這么做就完全沒有必要了。 因?yàn)榭梢灾付ㄖ岛褪录傩悦謥磉M(jìn)行基本的 Angular?雙向綁定語法,完全不用NgModel。
獨(dú)立的ngModel綁定相比直接綁定元素的原生屬性是個(gè)改進(jìn),但還能做得更好。
我們不應(yīng)該提及數(shù)據(jù)屬性兩次。Angular 應(yīng)該能捕捉組件的數(shù)據(jù)屬性,并用一條聲明來設(shè)置它——依靠[(ngModel)],可以這么做:
<input [(ngModel)]="currentHero.firstName">[(ngModel)]就是我們所需的一切嗎?有沒有什么理由需要回退到它的展開形式?
[(ngModel)]語法只能設(shè)置一個(gè)數(shù)據(jù)綁定屬性。 如果需要做更多或不同的事情,就得自己用它的展開形式。
來做點(diǎn)淘氣的事吧,比如強(qiáng)制讓輸入值變成大寫形式:
<input[ngModel]="currentHero.firstName" (ngModelChange)="setUpperCaseFirstName($event)">下面是實(shí)際操作中的所有變體形式,包括這個(gè)大寫版本:
?
內(nèi)置指令
上一版本的 Angular 中包含了超過 70 個(gè)內(nèi)置指令。 社區(qū)貢獻(xiàn)了更多,這還沒算為內(nèi)部應(yīng)用而創(chuàng)建的無數(shù)私有指令。
在新版的 Angular 中不需要那么多指令。 使用更強(qiáng)大、更富有表現(xiàn)力的 Angular 綁定系統(tǒng),其實(shí)可以達(dá)到同樣的效果。 如果能用簡單的綁定達(dá)到目的,為什么還要?jiǎng)?chuàng)建指令來處理點(diǎn)擊事件呢?
<button (click)="onSave()">Save</button>我們?nèi)匀豢梢詮暮喕瘡?fù)雜任務(wù)的指令中獲益。 Angular 發(fā)布時(shí)仍然帶有內(nèi)置指令,只是沒那么多了。 我們?nèi)詴?huì)寫自己的指令,只是沒那么多了。
下面來看一下那些最常用的內(nèi)置指令。
NgClass
我們經(jīng)常用動(dòng)態(tài)添加或刪除 CSS 類的方式來控制元素如何顯示。 通過綁定到NgClass,可以同時(shí)添加或移除多個(gè)類。
CSS 類綁定?是添加或刪除單個(gè)類的最佳途徑。
<!-- toggle the "special" class on/off with a property --> <div [class.special]="isSpecial">The class binding is special</div>當(dāng)想要同時(shí)添加或移除多個(gè)?CSS 類時(shí),NgClass指令可能是更好的選擇。
綁定到一個(gè) key:value 形式的控制對(duì)象,是應(yīng)用NgClass的好方式。這個(gè)對(duì)象中的每個(gè) key 都是一個(gè) CSS 類名,如果它的 value 是true,這個(gè)類就會(huì)被加上,否則就會(huì)被移除。
下面的組件方法setClasses管理了三個(gè) CSS 類的狀態(tài):
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類:
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>你既可以在初始化時(shí)調(diào)用setCurrentClassess(),也可以在所依賴的屬性變化時(shí)調(diào)用。
NgStyle
我們可以根據(jù)組件的狀態(tài)動(dòng)態(tài)設(shè)置內(nèi)聯(lián)樣式。?NgStyle綁定可以同時(shí)設(shè)置多個(gè)內(nèi)聯(lián)樣式。
樣式綁定是設(shè)置單一樣式值的簡單方式。
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" > This div is x-large. </div>如果要同時(shí)設(shè)置多個(gè)內(nèi)聯(lián)樣式,NgStyle指令可能是更好的選擇。
NgStyle需要綁定到一個(gè) key:value 控制對(duì)象。 對(duì)象的每個(gè) key 是樣式名,它的 value 是能用于這個(gè)樣式的任何值。
來看看組件的setCurrentStyles方法,它會(huì)根據(jù)另外三個(gè)屬性的狀態(tài)把組件的currentStyles屬性設(shè)置為一個(gè)定義了三個(gè)樣式的對(duì)象:
currentStyles: {}; setCurrentStyles() { this.currentStyles = { // CSS styles: set per current state of component properties 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px' }; }把NgStyle屬性綁定到currentStyles,以據(jù)此設(shè)置此元素的樣式:
<div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px). </div>你既可以在初始化時(shí)調(diào)用setCurrentStyles(),也可以在所依賴的屬性變化時(shí)調(diào)用。
NgIf
通過綁定NgIf指令到真值表達(dá)式,可以把元素子樹(元素及其子元素)添加到 DOM 上。
<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div>別忘了ngIf前面的星號(hào)(*)。 更多信息,見?* 與 <template>。
綁定到假值表達(dá)式將從 DOM 中移除元素子樹。
<!-- because of the ngIf guard`nullHero.firstName` never has a chance to fail --> <div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div> <!-- Hero Detail is not in the DOM because isActive is false--> <hero-detail *ngIf="isActive"></hero-detail>可見性和NGIF不是一回事
我們可以通過類綁定或樣式綁定來顯示和隱藏元素子樹(元素及其子元素)。
<!-- isSpecial is true --> <div [class.hidden]="!isSpecial">Show with class</div> <div [class.hidden]="isSpecial">Hide with class</div> <!-- HeroDetail is in the DOM but hidden --> <hero-detail [class.hidden]="isSpecial"></hero-detail> <div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div> <div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>隱藏子樹和用NgIf排除子樹是截然不同的。
當(dāng)隱藏子樹時(shí),它仍然留在 DOM 中。 子樹中的組件及其狀態(tài)仍然保留著。 即使對(duì)于不可見屬性,Angular 也會(huì)繼續(xù)檢查變更。 子樹可能占用相當(dāng)可觀的內(nèi)存和運(yùn)算資源。
當(dāng)NgIf為false時(shí),Angular 從 DOM 中物理地移除了這個(gè)元素子樹。 它銷毀了子樹中的組件及其狀態(tài),也潛在釋放了可觀的資源,最終讓用戶體驗(yàn)到更好的性能。
顯示 / 隱藏技術(shù)用在小型元素樹上可能還不錯(cuò)。 但在隱藏大樹時(shí)我們得小心;NgIf可能是更安全的選擇。但要記住:永遠(yuǎn)得先測(cè)量,再下結(jié)論。
NgSwitch
當(dāng)需要從一組可能的元素樹中根據(jù)條件顯示一個(gè)時(shí),我們就把它綁定到NgSwitch。 Angular 將只把選中的元素樹放進(jìn) DOM 中。
下面是例子:
<span [ngSwitch]="toeChoice"> <span *ngSwitchCase="'Eenie'">Eenie</span> <span *ngSwitchCase="'Meanie'">Meanie</span> <span *ngSwitchCase="'Miney'">Miney</span> <span *ngSwitchCase="'Moe'">Moe</span> <span *ngSwitchDefault>other</span> </span>我們把作為父指令的NgSwitch綁定到能返回開關(guān)值的表達(dá)式。 本例中,這個(gè)值是字符串,但它也可以是任何類型的值。
這個(gè)例子中,父指令NgSwitch控制一組<span>子元素。 每個(gè)<span>或者掛在匹配值表達(dá)式上,或者被標(biāo)記為默認(rèn)情況。
任何時(shí)候,這些?span?中最多只有一個(gè)會(huì)出現(xiàn)在 DOM 中。
如果這個(gè)?span?的匹配值等于開關(guān)值,Angular 就把這個(gè)<span>添加到 DOM 中。 如果沒有任何?span?匹配上,Angular 就把默認(rèn)的?span?添加到 DOM 中。 Angular 會(huì)移除并銷毀所有其它的?span。
可以用任何其它元素代替本例中的?span。 那個(gè)元素可以是帶有巨大子樹的<div>。 只有匹配的<div>和它的子樹會(huì)顯示在 DOM 中,其它的則會(huì)被移除。
這里有三個(gè)相互合作的指令:
ngSwitch:綁定到返回開關(guān)值的表達(dá)式
ngSwitchCase:綁定到返回匹配值的表達(dá)式
ngSwitchDefault:用于標(biāo)記出默認(rèn)元素的 attribute
不要在ngSwitch的前面加星號(hào) (*),而應(yīng)該用屬性綁定。
要把星號(hào) (*) 放在ngSwitchCase和ngSwitchDefault的前面。 要了解更多信息,見?* 與 <template>。
NgFor
NgFor是一個(gè)重復(fù)器指令 —— 自定義數(shù)據(jù)顯示的一種方式。
我們的目標(biāo)是展示一個(gè)由多個(gè)條目組成的列表。首先定義了一個(gè) HTML 塊,它規(guī)定了單個(gè)條目應(yīng)該如何顯示。 再告訴 Angular 把這個(gè)塊當(dāng)做模板,渲染列表中的每個(gè)條目。
下例中,NgFor應(yīng)用在一個(gè)簡單的<div>上:
<div *ngFor="let hero of heroes">{{hero.fullName}}</div>也可以把NgFor應(yīng)用在一個(gè)組件元素上,就下例這樣:
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>不要忘了ngFor前面的星號(hào) (*)。 更多信息,見?* 與 <template>
賦值給*ngFor的文本是用于指導(dǎo)重復(fù)器如何工作的指令。
NGFOR 微語法
賦值給*ngFor的字符串不是模板表達(dá)式。 它是一個(gè)微語法?—— 由 Angular 自己解釋的小型語言。在這個(gè)例子中,字符串"let hero of heroes"的含義是:
取出heroes數(shù)組中的每個(gè)英雄,把它存入局部變量hero中,并在每次迭代時(shí)對(duì)模板 HTML 可用
Angular 把這個(gè)指令翻譯成一組元素和綁定。
在前面的兩個(gè)例子中,ngFor指令在heroes數(shù)組上進(jìn)行迭代(它是由父組件的heroes屬性返回的), 以其所在的元素為模板“沖壓”出很多實(shí)例。 Angular 為數(shù)組中的每個(gè)英雄創(chuàng)建了此模板的一個(gè)全新實(shí)例。
hero前面的let關(guān)鍵字創(chuàng)建了名叫hero的模板輸入變量。
模板輸入變量和模板引用變量不是一回事!
在模板中使用這個(gè)變量來訪問英雄的屬性,就像在插值表達(dá)式中所做的那樣。 也可以把這個(gè)變量傳給組件元素上的綁定,就像對(duì)hero-detail所做的那樣。
帶索引的 NGFOR
ngFor指令支持可選的index,它在迭代過程中會(huì)從 0 增長到“數(shù)組的長度”。 可以通過模板輸入變量來捕獲這個(gè) index,并在模板中使用。
下例把 index 捕獲到名叫i的變量中,使用它“沖壓出”像 "1 - Hercules Son of Zeus" 這樣的條目。
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.fullName}}</div>要學(xué)習(xí)更多的類似 index?的值,例如last、even和odd,請(qǐng)參閱?NgFor API 參考。
NGFORTRACKBY
ngFor指令有時(shí)候會(huì)性能較差,特別是在大型列表中。 對(duì)一個(gè)條目的一丁點(diǎn)改動(dòng)、移除或添加,都會(huì)導(dǎo)致級(jí)聯(lián)的 DOM 操作。
例如,我們可以通過重新從服務(wù)器查詢來刷新英雄列表。 刷新后的列表可能包含很多(如果不是全部的話)以前顯示過的英雄。
我們知道這一點(diǎn),是因?yàn)槊總€(gè)英雄的id沒有變化。 但在 Angular 看來,它只是一個(gè)由新的對(duì)象引用構(gòu)成的新列表, 它沒有選擇,只能清理舊列表、舍棄那些 DOM 元素,并且用新的 DOM 元素來重建一個(gè)新列表。
如果給它一個(gè)追蹤函數(shù),Angular 就可以避免這種折騰。 追蹤函數(shù)告訴 Angular:我們知道兩個(gè)具有相同hero.id的對(duì)象其實(shí)是同一個(gè)英雄。 下面就是這樣一個(gè)函數(shù):
trackByHeroes(index: number, hero: Hero) { return hero.id; }現(xiàn)在,把NgForTrackBy指令設(shè)置為那個(gè)追蹤函數(shù)。
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>追蹤函數(shù)不會(huì)阻止所有 DOM 更改。 如果同一個(gè)英雄的屬性變化了,Angular 就可能不得不更新DOM元素。 但是如果這個(gè)屬性沒有變化 —— 而且大多數(shù)時(shí)候它們不會(huì)變化 —— Angular 就能留下這些 DOM 元素。列表界面就會(huì)更加平滑,提供更好的響應(yīng)。
這里是關(guān)于NgForTrackBy效果的插圖。
* 與 <template>
當(dāng)審視NgFor、NgIf和NgSwitch這些內(nèi)置指令時(shí),我們使用了一種古怪的語法:出現(xiàn)在指令名稱前面的星號(hào) (*)。
*是一種語法糖,它讓那些需要借助模板來修改 HTML 布局的指令更易于讀寫。?NgFor、NgIf和NgSwitch都會(huì)添加或移除元素子樹,這些元素子樹被包裹在<template>標(biāo)簽中。
我們沒有看到<template>標(biāo)簽,那是因?yàn)檫@種*前綴語法讓我們忽略了這個(gè)標(biāo)簽, 而把注意力直接聚焦在所要包含、排除或重復(fù)的那些 HTML 元素上。
這一節(jié),將深入研究一下,看看 Angular 是怎樣扒掉這個(gè)*,把這段 HTML 展開到<template>標(biāo)簽中的。
展開*ngIf
我們可以像 Angular 一樣,自己把*前綴語法展開成 template 語法,這里是*ngIf的一些代碼:
<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail>currentHero被引用了兩次,第一次是作為NgIf的真 / 假條件,第二次把實(shí)際的 hero 值傳給了HeroDetailComponent。
展開的第一步是把ngIf(沒有*前綴)和它的內(nèi)容傳給表達(dá)式,再賦值給template指令。
<hero-detail template="ngIf:currentHero" [hero]="currentHero"></hero-detail>下一步,也是最后一步,是把 HTML 包裹進(jìn)<template>標(biāo)簽和[ngIf]屬性綁定中:
<template [ngIf]="currentHero"> <hero-detail [hero]="currentHero"></hero-detail> </template>注意,[hero]="currengHero"綁定留在了模板中的子元素<hero-detail>上。
別忘了方括號(hào)!不要誤寫為ngIf="currentHero"! 這種語法會(huì)把一個(gè)字符串"currentHero"賦值給ngIf。 在 JavaScript 中,非空的字符串是真值,所以ngIf總會(huì)是true,而 Angular 將永遠(yuǎn)顯示hero-detail…… 即使根本沒有currentHero!
展開*ngSwitch
類似的轉(zhuǎn)換也適用于*ngSwitch。我們可以自己解開這個(gè)語法糖。 下例中,首先是*ngSwitchCase和*ngSwitchDefault,然后再解出<template>標(biāo)簽:
<span [ngSwitch]="toeChoice"> <!-- with *NgSwitch --> <span *ngSwitchCase="'Eenie'">Eenie</span> <span *ngSwitchCase="'Meanie'">Meanie</span> <span *ngSwitchCase="'Miney'">Miney</span> <span *ngSwitchCase="'Moe'">Moe</span> <span *ngSwitchDefault>other</span> <!-- with <template> --> <template [ngSwitchCase]="'Eenie'"><span>Eenie</span></template> <template [ngSwitchCase]="'Meanie'"><span>Meanie</span></template> <template [ngSwitchCase]="'Miney'"><span>Miney</span></template> <template [ngSwitchCase]="'Moe'"><span>Moe</span></template> <template ngSwitchDefault><span>other</span></template> </span>*ngSwitchWhen和*ngSwitchDefault用和*ngIf完全相同的方式展開,把它們以前的元素包裹在<template>標(biāo)簽中。
現(xiàn)在,應(yīng)該明白為什么ngSwitch本身不能用星號(hào) (*) 前綴了吧? 它沒有定義內(nèi)容,它的工作是控制一組模板。
上面這種情況下,它管理兩組NgSwitchCase和NgSwitchDefault指令,一次是 (*) 前綴的版本,一次是展開模板后的版本。 我們也期待它顯示所選模板的值兩次。這正是在這個(gè)例子中看到的:
展開*ngFor
*ngFor也經(jīng)歷類似的轉(zhuǎn)換。從一個(gè)*ngFor的例子開始:
<hero-detail *ngFor="let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>這里是在把ngFor傳進(jìn)template指令后的同一個(gè)例子:
<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>下面,它被進(jìn)一步擴(kuò)展成了包裹著原始<hero-detail>元素的<template>標(biāo)簽:
<template ngFor let-hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes"> <hero-detail [hero]="hero"></hero-detail> </template>NgFor的代碼相對(duì)NgIf更復(fù)雜一點(diǎn),因?yàn)橹貜?fù)器有更多活動(dòng)部分需要配置。 這種情況下,我們就得記住添加NgForOf指令和NgForTrackBy指令,并對(duì)它們賦值。 使用*ngFor語法比直接寫這些展開后的 HTML 本身要簡單多了。
模板引用變量
模板引用變量是模板中對(duì) DOM 元素或指令的引用。
它能在原生 DOM 元素中使用,也能用于 Angular 組件 —— 實(shí)際上,它可以和任何自定義 Web 組件協(xié)同工作。
引用模板引用變量
可以在同一元素、兄弟元素或任何子元素中引用模板引用變量。
不要在同一個(gè)模版中多次定義相同變量名,否則運(yùn)行時(shí)的值將會(huì)不可預(yù)測(cè)。
這里是關(guān)于創(chuàng)建和使用模板引用變量的另外兩個(gè)例子:
<!-- phone refers to the input element; pass its `value` to an event handler --> <input #phone placeholder="phone number"> <button (click)="callPhone(phone.value)">Call</button> <!-- fax refers to the input element; pass its `value` to an event handler --> <input ref-fax placeholder="fax number"> <button (click)="callFax(fax.value)">Fax</button>"phone" 的井號(hào) (#) 前綴表示定義了一個(gè)phone變量。
有些人不喜歡使用#字符,而是使用它的規(guī)范形式:ref-前綴。 例如,既能用#phone,也能用ref-phone來定義phone變量。
如何獲取變量的值
Angular 把這種變量的值設(shè)置為它所在的那個(gè)元素。 在這個(gè)input元素上定義了這些變量。 把那些input元素對(duì)象傳給 button 元素,在事件綁定中,它們作為參數(shù)傳給了call方法。
NgForm 和模板引用變量
讓我們看看最后一個(gè)例子:表單,使用模板引用變量的典范。
正如在表單一章中所見過的,表單的 HTML 可以做得相當(dāng)復(fù)雜。 下面是簡化過的范例 —— 雖然仍算不上多簡單。
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input class="form-control" name="name" required [(ngModel)]="currentHero.firstName"> </div> <button type="submit" [disabled]="!theForm.form.valid">Submit</button> </form>模板引用變量theForm在這個(gè)例子中出現(xiàn)了三次,中間隔著一大段 HTML。
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm"> <button type="submit" [disabled]="!theForm.form.valid">Submit</button> </form>theForm變量的值是什么?
如果 Angular 沒有接管它,那它可能是個(gè)HTMLFormElement。 實(shí)際上它是個(gè)ngForm,對(duì) Angular 內(nèi)置指令NgForm的引用。 它包裝了原生的HTMLFormElement并賦予它更多超能力,比如跟蹤用戶輸入的有效性。
這解釋了該如何通過檢查theForm.form.valid來禁用提交按鈕,以及如何把一個(gè)信息量略大的對(duì)象傳給父組件的onSubmit方法。(譯注:onSubmit方法可能會(huì)出發(fā)事件,被父組件監(jiān)聽,參見下面的輸入和輸出屬性和父組件監(jiān)聽子組件的事件。)
輸入與輸出屬性
迄今為止,我們主要聚焦在綁定聲明的右側(cè),學(xué)習(xí)如何在模板表達(dá)式和模板語句中綁定到組件成員。 當(dāng)成員出現(xiàn)在這個(gè)位置上,則稱之為數(shù)據(jù)綁定的源。
本節(jié)則專注于綁定到的目標(biāo),它位于綁定聲明中的左側(cè)。 這些指令的屬性必須被聲明成輸入或輸出。
記住:所有組件皆為指令。
我們要重點(diǎn)突出下綁定目標(biāo)和綁定源的區(qū)別。
綁定的目標(biāo)是在=左側(cè)的部分,?源則是在=右側(cè)的部分。
綁定的目標(biāo)是綁定符:[]、()或[()]中的屬性或事件名,?源則是引號(hào) (" ") 中的部分或插值符號(hào) ({{}}) 中的部分。
源指令中的每個(gè)成員都會(huì)自動(dòng)在綁定中可用。 不需要特別做什么,就能在模板表達(dá)式或語句中訪問指令的成員。
訪問目標(biāo)指令中的成員則受到限制。 只能綁定到那些顯式標(biāo)記為輸入或輸出的屬性。
在下面的例子中,iconUrl和onSave是組件的成員,它們?cè)?#61;右側(cè)引號(hào)語法中被引用了。
<img [src]="iconUrl"/> <button (click)="onSave()">Save</button>它們既不是組件的輸入也不是輸出。它們是綁定的數(shù)據(jù)源。
現(xiàn)在,看看HeroDetailComponent,它是綁定的目標(biāo)。
<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)"> </hero-detail>HeroDetailComponent.hero和HeroDetailComponent.deleteRequest都在綁定聲明的左側(cè)。?HeroDetailComponent.hero在方括號(hào)中,它是屬性綁定的目標(biāo)。?HeroDetailComponent.deleteRequest在圓括號(hào)中,它是事件綁定的目標(biāo)。
聲明輸入和輸出屬性
目標(biāo)屬性必須被顯式的標(biāo)記為輸入或輸出。
當(dāng)我們深入HeroDetailComponent內(nèi)部時(shí),就會(huì)看到這些屬性被裝飾器標(biāo)記成了輸入和輸出屬性。
@Input() hero: Hero; @Output() deleteRequest = new EventEmitter<Hero>();另外,還可以在指令元數(shù)據(jù)的inputs或outputs數(shù)組中標(biāo)記出這些成員。比如這個(gè)例子:
@Component({inputs: ['hero'], outputs: ['deleteRequest'], })既可以通過裝飾器,也可以通過元數(shù)據(jù)數(shù)組來指定輸入/輸出屬性。但別同時(shí)用!
輸入還是輸出?
輸入屬性通常接收數(shù)據(jù)值。?輸出屬性暴露事件生產(chǎn)者,如EventEmitter對(duì)象。
輸入和輸出這兩個(gè)詞是從目標(biāo)指令的角度來說的。
從HeroDetailComponent角度來看,HeroDetailComponent.hero是個(gè)輸入屬性, 因?yàn)閿?shù)據(jù)流從模板綁定表達(dá)式流入那個(gè)屬性。
從HeroDetailComponent角度來看,HeroDetailComponent.deleteRequest是個(gè)輸出屬性, 因?yàn)槭录哪莻€(gè)屬性流出,流向模板綁定語句中的處理器。
給輸入/輸出屬性起別名
有時(shí)需要讓輸入/輸出屬性的公開名字不同于內(nèi)部名字。
這是使用?attribute 指令時(shí)的常見情況。 指令的使用者期望綁定到指令名。例如,在<div>上用myClick選擇器應(yīng)用指令時(shí), 希望綁定的事件屬性也叫myClick。
<div (myClick)="clickMessage=$event">click with myClick</div>然而,在指令類中,直接用指令名作為自己的屬性名通常都不是好的選擇。 指令名很少能描述這個(gè)屬性是干嘛的。?myClick這個(gè)指令名對(duì)于用來發(fā)出 click 消息的屬性就算不上一個(gè)好名字。
幸運(yùn)的是,可以使用約定俗成的公開名字,同時(shí)在內(nèi)部使用不同的名字。 在上面例子中,實(shí)際上是把myClick這個(gè)別名指向了指令自己的clicks屬性。
把別名傳進(jìn)@Input/@Output裝飾器,就可以為屬性指定別名,就像這樣:
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...也可在inputs和outputs數(shù)組中為屬性指定別名。 可以寫一個(gè)冒號(hào) (:) 分隔的字符串,左側(cè)是指令中的屬性名,右側(cè)則是公開的別名。
@Directive({outputs: ['clicks:myClick'] // propertyName:alias })模板表達(dá)式操作符
模板表達(dá)式語言使用了 JavaScript 語法的子集,并補(bǔ)充了幾個(gè)用于特定場(chǎng)景的特殊操作符。 下面介紹其中的兩個(gè):管道和安全導(dǎo)航操作符。
?
管道操作符 ( | )
在綁定之前,表達(dá)式的結(jié)果可能需要一些轉(zhuǎn)換。例如,可能希望把數(shù)字顯示成金額、強(qiáng)制文本變成大寫,或者過濾列表以及進(jìn)行排序。
Angular?管道對(duì)像這樣的小型轉(zhuǎn)換來說是個(gè)明智的選擇。 管道是一個(gè)簡單的函數(shù),它接受一個(gè)輸入值,并返回轉(zhuǎn)換結(jié)果。 它們很容易用于模板表達(dá)式中,只要使用管道操作符 (|)?就行了。
<div>Title through uppercase pipe: {{title | uppercase}}</div>管道操作符會(huì)把它左側(cè)的表達(dá)式結(jié)果傳給它右側(cè)的管道函數(shù)。
還可以通過多個(gè)管道串聯(lián)表達(dá)式:
<!-- Pipe chaining: convert title to uppercase, then to lowercase --> <div> Title through a pipe chain: {{title | uppercase | lowercase}} </div>還能對(duì)它們使用參數(shù):
<!-- pipe with configuration argument => "February 25, 1970" --> <div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>json管道對(duì)調(diào)試綁定特別有用:
<div>{{currentHero | json}}</div>它生成的輸出是類似于這樣的:
{ "firstName": "Hercules", "lastName": "Son of Zeus", "birthdate": "1970-02-25T08:00:00.000Z", "url": "http://www.imdb.com/title/tt0065832/", "rate": 325, "id": 1 }?
安全導(dǎo)航操作符 ( ?. ) 和空屬性路徑
Angular 的安全導(dǎo)航操作符 (?.)?是一種流暢而便利的方式,用來保護(hù)出現(xiàn)在屬性路徑中 null 和 undefined 值。 下例中,當(dāng)currentHero為空時(shí),保護(hù)視圖渲染器,讓它免于失敗。
The current hero's name is {{currentHero?.firstName}}我們來詳細(xì)闡述一下這個(gè)問題和解決方案:
如果下列數(shù)據(jù)綁定中title屬性為空,會(huì)發(fā)生什么?
The title is {{title}}這個(gè)視圖仍然被渲染出來,但是顯示的值是空;只能看到 “The title is”,它后面卻沒有任何東西。 這是合理的行為。至少應(yīng)用沒有崩潰。
假設(shè)模板表達(dá)式涉及屬性路徑,在下例中,顯示一個(gè)空 (null) 英雄的firstName。
The null hero's name is {{nullHero.firstName}}JavaScript 拋出了空引用錯(cuò)誤,Angular 也是如此:
TypeError: Cannot read property 'firstName' of null in [null].暈,整個(gè)視圖都不見了。
如果確信hero屬性永遠(yuǎn)不可能為空,可以聲稱這是合理的行為。 如果它必須不能為空,但它仍然是空值,實(shí)際上是制造了一個(gè)編程錯(cuò)誤,它應(yīng)該被捕獲和修復(fù)。 這種情況應(yīng)該拋出異常。
另一方面,屬性路徑中的空值可能會(huì)時(shí)常發(fā)生,特別是當(dāng)我們知道數(shù)據(jù)最終會(huì)出現(xiàn)。
當(dāng)?shù)却龜?shù)據(jù)的時(shí)候,視圖渲染器不應(yīng)該抱怨,而應(yīng)該把這個(gè)空屬性路徑顯示為空白,就像上面title屬性那樣。
不幸的是,當(dāng)currentHero為空的時(shí)候,應(yīng)用崩潰了。
可以通過寫NgIf代碼來解決這個(gè)問題。
<!--No hero, div not displayed, no error --> <div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>或者可以嘗試通過&&來把屬性路徑的各部分串起來,讓它在遇到第一個(gè)空值的時(shí)候,就返回空。
The null hero's name is {{nullHero && nullHero.firstName}}這些方法都有價(jià)值,但是會(huì)顯得笨重,特別是當(dāng)這個(gè)屬性路徑非常長的時(shí)候。 想象一下在一個(gè)很長的屬性路徑(如a.b.c.d)中對(duì)空值提供保護(hù)。
Angular 安全導(dǎo)航操作符 (?.) 是在屬性路徑中保護(hù)空值的更加流暢、便利的方式。 表達(dá)式會(huì)在它遇到第一個(gè)空值的時(shí)候跳出。 顯示是空的,但應(yīng)用正常工作,而沒有發(fā)生錯(cuò)誤。
<!-- No hero, no problem! --> The null hero's name is {{nullHero?.firstName}}在像a?.b?.c?.d這樣的長屬性路徑中,它工作得很完美。
轉(zhuǎn)載于:https://www.cnblogs.com/shitoupi/p/6622878.html
總結(jié)
以上是生活随笔為你收集整理的angular2的模板语法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件测试之Selenium IDE
- 下一篇: POJ 1661 Help Jimmy