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