在mvc/mvvm類框架出現之前,開發者通常需要手動更新html並維護html與數據之間的關係。隨著mvc思想在前端社區的普及和發展,view層和model層的解耦和分離機制已經是各框架的標配了。令人欣喜的是,angular2在現有各框架的理論基礎上對數據綁定重新進行了抽象,在架構上進行了革新,很有 ...
在mvc/mvvm類框架出現之前,開發者通常需要手動更新html並維護html與數據之間的關係。隨著mvc思想在前端社區的普及和發展,view層和model層的解耦和分離機制已經是各框架的標配了。
令人欣喜的是,angular2在現有各框架的理論基礎上對數據綁定重新進行了抽象,在架構上進行了革新,很有借鑒意義。從本文起我們就將開始討論angular2中的數據綁定。
angular2中有四種數據綁定:插入符(interpolation)、單向綁定(one-way binding)、事件綁定(event binding)和雙向綁定(two-way binding)。今天我們先介紹前兩種:插入符和單向綁定。
說到數據綁定就不得不提到模板層和數據。在angular1中,以scope屬性的形式存在的數據會通過watch機制來和模板進行綁定。具體操作中,既可以手動調用scope上的$watch方法,也可以在模板層中使用相關的綁定指令或大括弧綁定語法。然而,direcitve和scope的關係卻錯綜複雜:directive既可以復用父級的scope,也可以擁有自己的scope;當它擁有自己的scope時,這個scope既可以和父級scope沒有繼承關係,也可以通過prototype鏈來繼承父級scope上的屬性……總之,這種盤根錯節的數據關係使得angular1對於開發者並不那麼友好。自然,簡化數據關係和數據結構就成了Google在angular2開發中的重要任務之一。
在angular2中,component和數據有了清晰的對應關係:模板層中綁定的數據就是當前組件的實例屬性,scope的概念消失了。
我們先通過一些簡單的例子來幫助大家認識一下這種關係:
@Component({ // selector告知angular在哪裡初始化AppComponent這個組件 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 class="{{color}}">hello world, from {{name}}</h1> ` }) export class AppComponent { public name: string = 'Ralph'; };
我們在AppComponent類中添加了string類型的name屬性,並賦值為"Ralph";相應地,為了使name屬性能夠在模板中被顯示出來,我們在<h1>標簽中增加了{{name}},這就是angular2模板的插入符語法。
插入符語法不僅能用到html標簽的內容上,也可以用到屬性上:
import {Component} from '@angular/core'; @Component({ styles: [` .blue{ background: blue; } `], // selector告知angular在哪裡初始化AppComponent這個組 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 class="{{classNames}}">hello world, from {{name}}</h1> ` }) export class AppComponent { public name: string = 'Ralph'; public classNames: string = 'blue'; };
在上面的例子中,我們在Component裝飾器上增加了styles屬性,styles屬性的值是一個字元串數組,編譯後將作為當前組件的css;同時,我們還使用了es6中的反引號語法來包裹整塊字元串,省去了拼接的麻煩。h1上的class屬性和AppComponent上的classNames屬性進行了綁定,classNames被賦值為了blue,正好適配了class中的.blue選擇器。
在進行屬性綁定時,我們也可以使用單向綁定的語法:
import {Component} from '@angular/core'; @Component({ styles: [` .blue{ background: blue; } `], // selector告知angular在哪裡初始化AppComponent這個組 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 [class]="classNames">hello world, from {{name}}</h1> ` }) export class AppComponent { public name: string = 'Ralph'; public classNames: string = 'blue'; };
請註意class兩端的括弧[]不能省略,否則只會在初始值時賦值,而不會對變化進行監聽。
單向綁定語法還有一種較少使用的形式,把中括弧換為bind-首碼,功能和上例等價:
import {Component} from '@angular/core'; @Component({ styles: [` .blue{ background: blue; } `], // selector告知angular在哪裡初始化AppComponent這個組 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 bind-class="classNames">hello world, from {{name}}</h1> ` }) export class AppComponent { public name: string = 'Ralph'; public classNames: string = 'blue'; };
除了引入組件屬性並放棄scope外,angular2在框架思想上還有一個比較有價值的貢獻:徹底區分dom node屬性(property)和html標簽屬性(attribute)。
由於中文表述上property和attribute都翻譯為“屬性”,國內的開發者對於這兩個截然不同的概念可能相對缺少關註;然而在國外,如何區分property和attribute是最高頻出現的前端基礎面試題之一。既然大家都知道他們不同,那博主為什麼還說這是angular2的貢獻呢?angular2之前的框架,在遇到property和attribute相關的問題時,都只是“頭痛醫頭,腳痛醫腳”,只解決具體問題而沒有抽象到框架層面上去。以react為例,在文檔的forms章節中(https://facebook.github.io/react/docs/forms.html),認為input控制項的value property和value attribute之間的不同步是傳統html的一種缺失(absent when writing traditional form HTML);而angular2的態度相對更巨集觀一些:property和attribute不是同一個概念,二者之間沒有絕對的對應關係,它們可以同步,也可以不同步,一些property在attribute中找不到對應,也有一些attribute在property中找不到對應;但是要記住一點,angular2的綁定是針對dom node property的綁定,而非針對HTML標簽attribute的綁定。
在這樣的設計之上,對於value、disabled、checked等屬性的處理就很清晰了;更為重要的是,它也為指令間、組件間的數據傳遞打下了良好的基礎。
以checked屬性的綁定為例:
import {Component} from '@angular/core'; @Component({ // selector告知angular在哪裡初始化AppComponent這個組件 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 class="{{color}}">hello world</h1> checked屬性綁定測試:<input type="checkbox" [checked]="checked" /><br /> <button (click)="onClick()">click me</button> ` }) export class AppComponent { public checked: boolean = false; public onClick() { this.checked = !this.checked; } };
上面的例子中出現了對click事件的監聽,暫時不是本節的重點,大家可以先忽略細節。input[type="checkbox"]上的checked屬性和AppComponent上的checked屬性進行了綁定。AppComponent上的checked屬性初始值為false,用戶每次點擊按鈕時,onClick方法被觸發,checked的值會相對於上一個值取反,從而實現選中/反選多選框的效果。
style和class是兩個比較特殊的屬性:style作為行內屬性時,其值依然是鍵值對的組合;class經常需要對某一個值進行添加和刪除,以完成toggle的操作。angular2對style和class的綁定有進一步的封裝。
對style進行綁定:
import {Component} from '@angular/core'; @Component({ // selector告知angular在哪裡初始化AppComponent這個組件 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 class="{{color}}">hello world</h1> <button (click)="onClick()" [style.background]="color">更改背景色</button> ` }) export class AppComponent { public color: string = 'blue'; public onClick() { this.color = this.color === 'blue' ? 'green' : 'blue'; } };
對class進行綁定:
import {Component} from '@angular/core'; @Component({ styles: [` .selected{ background: blue; } `], // selector告知angular在哪裡初始化AppComponent這個組件 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 class="{{color}}">hello world</h1> <button (click)="onClick()" [class.selected]="open">更改背景色</button> ` }) export class AppComponent { public open: boolean = false; public onClick() { this.open = !this.open; } };
需要對style和class進行操作時,除了使用數據綁定以外,也可以使用相應的指令,稍後的章節中會有所涉及。
既然angular2的數據綁定通常是對dom node property的綁定,那麼需要綁定一個不具有property對應項的attribute屬性怎麼操作呢?作為一個例外,angular2為此提供了attr.attribute-name的綁定方式,以綁定aria-label屬性為例:
import {Component} from '@angular/core'; @Component({ // selector告知angular在哪裡初始化AppComponent這個組件 selector: 'hello-world', // AppComponent組件的具體模板 template: ` <h1 class="{{color}}">hello world</h1> <button [attr.aria-label]="type">help</button> ` }) export class AppComponent { public type: string = 'help'; };
對angular2的插入符和單向綁定的介紹就先到這裡,事件綁定、雙向綁定將放到後續的章節中。