說到AngularJS,咱們首先想到的大概也就是雙向數據綁定和指令系統了,這二者也是AngularJS中最爲吸引人的地方。雙向數據綁定呢,感受沒什麼好說的,那麼今天咱們就來簡單的討論下AngularJS這個框架的指令系統,本人也是初學,查閱了一些資料,要是有一些說的很差的地方,萬望指出。css
指令做爲AngularJS中最爲重要的部分,因此這個框架自己也是自帶了比較多的的指令,可是在開發中,這些指令一般不能知足咱們的須要,因此咱們也是須要自定義一些指令的。那麼一個AngularJS指令在HTML代碼中能夠有四種表現形式:數組
一、做爲一個新的HTML元素來使用。app
<hello></hello>或者<hello/>
二、做爲一個元素的屬性來使用框架
<div hello></div>
三、做爲一個元素的類來使用函數
<div class="hello"></div>
四、做爲註釋來使用性能
<!--directive: hello -->
注意這裏有一個陷阱,就是在「directive: hello」這個的後面要有一個空格,否則的話是沒有效果的,同時推薦註釋的方法的仍是少用,若是非要追求高大上,那就隨性吧。既然指令有以上四種表現形式,那麼具體他是怎麼來定義的呢?學習
.directive('hello',function(){ return { restrict:'AECM', template:'<button>click me</button>' } })
上面就是定義一個指令最簡單的代碼了,沒有之一。在上面的代碼中,directive()這個方法定義了一個新的指令,該方法有兩個參數,第一個'hello'就是規定指令的名字爲hello,第二個參數是返回指令對象的函數。那麼在上面的代碼中,該函數主要使用了兩個屬性來定義這個hello指令:ui
一、restrict[string]這個屬性,主要是用來規定指令在HTML代碼中可使用什麼表現形式。A表明屬性、E表明元素、C表明類、M表明註釋。實際狀況中咱們通常使用AE這兩種方式。this
二、template[string or function]這個屬性,規定了指令被Angular編譯和連接(link)後生成的HTML標記,這個屬性能夠簡單到只有一個HTML文本在裏面,也能夠特別複雜,當該屬性的值爲function的時候,那麼該方法返回的就是表明模板的字符串,同時也能夠在裏面使用{{}}這個表達式。spa
template: function () { return '<button>click me</button>'; }
可是在通常狀況下,template這個屬性都會被templateUrl取代掉,用它來指向一個外部的文件地址,因此咱們一般把模板放在外部的一個HTML文件中,而後使用templateUrl來指向他。
在定義指令的時候,除了以上兩個最基礎的屬性外,咱們還會使用到其餘的不少屬性,那麼下面咱們就來一一的說下:
一、priority[number]屬性,這個屬性是來規定自定義的指令的優先級的,當一個DOM元素上面有一個以上的指令的時候,就須要去比較指令的優先級了,優先級高的指令先執行。這個優先級就是用來在執行指令的compile函數前,先排序的,那麼關於compile這個函數,咱們會在下面仔細的說下。
二、terminal[boolean]屬性,該參數用來定義是否中止當前元素上比本指令優先級低的指令,若是值爲true,就是正常狀況,按照優先級高低的順序來執行,若是設置爲false,就不會執行當前元素上比本指令優先級低的指令。
三、replace[boolean]屬性,這個屬性用來規定生成的HTML內容是否會替換掉定義此指令的HTML元素。當咱們把該屬性的值設爲true的時候,打開控制檯看看你會發現這個指令生成的元素會是這樣的:
當咱們設置爲false的時候會是這樣的:
。
四、link[function]屬性,在上面的例子中,咱們自定義的指令其實沒有多大意義,這只是一個最簡單的指令,有好多的屬性咱們都沒有爲他定義,因此沒有多大用途。好比這個link函數,它包括三個參數:scope、element、attrs。這個link函數主要是用來添加對DOM元素的事件監聽、監視模型屬性變化、以及更新DOM的。它裏面三個參數:
一:scope參數,在咱們沒有爲指令定義scope屬性的時候,那麼他表明的就是父controller的scope。
二:element參數,就是指令的jQLite(jQuery的子集)包裝DOM元素。若是你在引入AngularJS以前引入了jQuery,那麼這個元素就是jQuery元素,而不是jQLite元素。因爲這個元素已經被jQuery/jQLite包裝了,因此咱們就在進行DOM操做的時候就不須要再使用 $()來進行包裝。
三:attrs參數,它包含了該指令所在元素的屬性的標準化參數對象。
五、scope[boolean or object]屬性,該屬性是用來定義指令的scope的範圍,默認狀況下是false,也就是說該指令繼承了父controller的scope,能夠隨意的使用父controller的scope裏的屬性,可是這樣的話就會污染到父scope裏的屬性,這樣是不可取的。因此咱們可讓scope取如下兩個值:true和{}。
當爲true的時候,表示讓Angular給指令建立一個繼承於父scope的scope。
var myapp=angular.module('myapp',[]) .controller('myctrl',['$scope', function ($scope) { $scope.color='red'; }]) .directive('hello', function () { return{ restrict:'AECM', replace:true, template:'<button ng-click="sayhello()" style="background-color: {{color}}">click me</button>', scope:true, link: function (scope,elements,attrs) { elements.bind('click', function () { elements.css('background-color','blue'); }) } } })
這裏咱們爲父scope定義了一個color的屬性,並賦值爲red,在hello指令的scope屬性中,咱們給了true,因此angular就爲這個指令建立了一個繼承於父scope的scope,而後在template屬性中,咱們用{{}}使用了從父scope中繼承過來的color屬性,因此按鈕會是紅色的。
當爲{}的時候,表示建立一個隔離的scope,不會繼承父scope的屬性。可是在有的時候咱們也要須要訪問父scope裏的屬性或者方法,那麼咱們應該怎麼辦呢。angular早就爲咱們想到了這一點,有如下的三個辦法可讓咱們記性上面的操做:
一:使用@實現單向綁定,若是咱們只給scope的這個{}值的話,那麼上面代碼的按鈕的背景色將會是灰色的。,而若是咱們須要使用父scope的color屬性的時候,咱們能夠這樣寫:
scope{ color:'@color' }
<hello color="{{color}}"></hello>
這裏有兩點須要注意:一、scope裏的屬性color表明的是模板{{}}這個表達式裏面的color,二者必須一致。二、scope裏的屬性color的值,也就是@後面的color,表示的是下面的HTML元素裏的屬性color,因此這二者也必須一致,當這裏的屬性名和模板裏表達式{{}}裏面使用的名稱相同的話,就能夠省略掉@後面的屬性名了,而後寫成下面的形式。
scope{ color:'@' }
從指令中scope的值能夠看出,指令模板中的表達式{{}}裏的color的指向的是當前元素元素的color屬性,這個color屬性的值就是父scope的屬性color的值。父scope把他的color屬性值傳遞給了當前元素的color屬性,而後color屬性又把值傳遞給了模板中表達式裏的color,這個過程是單向的。
二:使用=實現雙向綁定
.directive('hello', function () { return{ restrict:'AECM', replace:true, template:'<button style="background-color: {{color}}">click me</button>', scope:{ color:'=' }, link: function (scope,elements,attrs) { elements.bind('click', function () { elements.css('background-color','blue'); scope.$apply(function () { scope.color='pink'; }) }) } } })
<hello color="color"></hello> <input type="text" ng-model="color"/>
這裏咱們給指令的scope中的color屬性和父scope中的color屬性進行了雙向綁定,而且給指令的link函數裏,添加了一個單擊事件,點擊按鈕會讓按鈕的顏色發生變化,而且改變指令scope的color屬性的值,再給HTML頁面中加了一個input標籤,輸出或者輸入父scope的color屬性的值。這裏有一個地方須要注意:當前元素的屬性名的值不用再加上{{}}這個表達式了,由於這裏父scope傳遞的是一個真實的scope數據模型,而不是簡單的字符串,因此這樣咱們就能夠傳遞簡單的字符串、數組、甚至複雜的對象給指令的scope。如今讓咱們來看看點擊這個按鈕將會發生什麼。
這裏咱們能看到,按鈕的顏色變成了粉色的,說明點擊讓指令的scope的color屬性發生了變化,從而致使按鈕的顏色發生了變化。可是這裏不只僅是按鈕發生了變化,注意看,input表單裏的值也變成了pink,這就說明父scope的color屬性也發生了變化。 另外,再讓咱們來給input裏面輸入一個顏色,看看發生什麼變化。
,能夠看出當咱們在表單裏輸入另一種顏色的時候,按鈕的顏色也發生了變化,這就說明指令的scope的color屬性被改變了。綜上咱們能夠發現使用'='實現的是雙向綁定。
三:使用&調用父scope裏的方法
var myapp=angular.module('myapp',[]) .controller('myctrl',['$scope', function ($scope) { $scope.color='red'; $scope.sayhello= function () { alert('hello'); }; }]) .directive('hello', function () { return{ restrict:'AECM', replace:true, template:'<button ng-click="sayhello()" style="background-color: {{color}}">click me</button>', scope:{ color:'=', sayhello:'&' }, link: function (scope,elements,attrs) { elements.bind('click', function () { elements.css('background-color','blue'); scope.$apply(function () { scope.color='pink'; }) }) } } })
<hello color="color" sayhello="sayhello()"></hello> <input type="text" ng-model="color"/>
這裏咱們也有兩個地方須要注意:一、咱們不只須要在模板中使用ng-click指令,綁定上要調用的父scope中的方法,並且要在給當前元素添加一個屬性,而且這個屬性指向要調用的父scope的方法。二、指令scope的屬性sayhello、當前元素的屬性sayhello、模板綁定的事件方法名sayhello這三者要一致。那麼這樣咱們就能夠點擊按鈕,彈出一個對話框了。
六、transclude[boolean]屬性,這個屬性用來讓咱們規定指令是否能夠包含任意內容
.directive('hello', function () { return{ restrict:'AECM', replace:true, transclude:true, scope:{}, template:'<div ng-transclude></div>', } })
<hello> hello <span>{{color}}</span> </hello>
當他的值爲true的時候,這是頁面上輸出的值。當爲false的時候,頁面上將會是空白的。這裏有一個地方須要注意,就是<span>{{color}}</span>,這裏的color是父scope裏的color。並非指令裏的scope的color屬性。
七、compile[function]參數,該方法有兩個參數element,attrs,第一個參數element指指令所在的元素,第二個attrs指元素上賦予的參數的標準化列表。這裏咱們也有個地方須要注意:compile 函數不能訪問 scope,而且必須返回一個 link 函數。可是若是沒有設置 compile 函數,你能夠正常地配置 link 函數,(有了compile,就不能用link,link函數由compile返回)。
.directive('hello', function () { return{ restrict:'AECM', replace:true, translude:true, template:'<button ng-click="sayhello()" style="background-color: {{color}}">click me</button>', scope:{ color:'=', sayhello:'&' }, compile: function (element,attrs) { return function (scope,elements,attrs) { elements.bind('click', function () { elements.css('background-color','blue'); scope.$apply(function () { scope.color='pink'; }) }) }; } } })
如今讓咱們來點擊這個按鈕
咱們發現,這裏點擊按鈕以後發生的事情和前面用link屬性的同樣,這實際上是沒有多少差異的。
其實在大多數的狀況下,咱們只須要使用 link 函數。這是由於大部分的指令只須要考慮註冊事件監聽、監視模型、以及更新DOM等,這些均可以在 link 函數中完成。 可是對於像 ng-repeat 之類的指令,須要克隆和重複 DOM 元素屢次,在 link 函數執行以前由 compile 函數來完成。那麼爲何咱們須要兩個分開的函數來完成生成過程,爲何不能只使用一個?要回答好這個問題,咱們須要理解指令在Angular中是如何被編譯的!
八、指令是如何被編譯的
當咱們的angular應用引導啓動的時候,angular將會使用$compile服務遍歷DOM元素,在全部的指令都被識別以後,將會調用指令的compile方法,返回一個link函數,而後將這個link函數添加到稍後執行的 link 函數列表中,這個過程被稱爲編譯階段。像ng-repeat這樣的指令,須要被重複克隆不少次,compile函數只在編譯階段被執行一次,而且複製這些模板,可是link 函數會針對每一個被複制的實例被執行。因此分開處理,讓咱們在性能上有必定的提升(這句話有點不太理解,我是從別的地方copy過來的。原文在這裏http://blog.jobbole.com/62249/)。
九、controller[string or function]和require[string or string[]]參數,當咱們想要容許其餘的指令和你的指令發生交互時,咱們就須要使用 controller 函數。當另外一個指令想要交互時,它須要聲明它對你的指令 controller 實例的引用(require)。
.directive('hello', function () { return{ scope:{}, require:'^he', compile: function (element,attrs) { return function (scope,elements,attrs,cntIns) { cntIns.fn() }; } } }) .directive('he', function () { return { restrict:'AE', scope:{}, controller: function ($scope, $compile, $http) { this.fn= function () { alert('hello'); }; } } })
<he> <hello color="color" sayhello="sayhello()"></hello> </he>
當頁面加載完畢以後,會彈出一個對話框。
好了上面就是我這段時間學習angular,所瞭解到的指令的知識,就先寫到這裏了。