本文執行個體講述了AngularJS指令用法。分享給大家供大家參考,具體如下:
指令(directives)是任何AngularJS應用中最重要的成分。儘管AngularJS已經內建了很多指令,你經常會發現需要自己親手建立一些特別的指令。本文將會帶你瞭解自訂指令並解釋如何在現實世界中的Angular項目中使用它們。文章的最後,我們將一起用Angular指令來建立一個簡單的筆記小應用。
綜述
一個指令就是一個引入新文法的東西。指令是在DOM元素上做的標記,並同時附加了一些特定的行為。例如,靜態HTML並不知道如何來建立並顯示一個日期選擇外掛程式。為了將這個新文法教給HTML我們需要一條指令。這個指令將會建立一個充當日期選取器的元素。我們將在隨後看到如何?這個指令。
如果你之前已經編寫過Angular應用,那麼你已經使用過指令了,不管你有沒有意識到這點。你可能已經使用過像是ng-model,ng-repeat,ng-show等等這樣的指令。所有這些指令都將特定的功能綁定到了DOM元素之上。例如,ng-repeat會重複特定的元素,而ng-show會有條件的展示元素。如果你想要建立一個可拖動元素的話你可能需要建立一個指令。指令背後的基本思想很簡單。它通過在元素上綁定事件監聽器並且將DOM變形來使HTML變得具有互動性。
從jQuery的角度來看指令
想想你如何使用jQuery來建立一個日期選取器。我們首先在HTML中添加一個普通的input欄位然後在jQuery中我們調用$(element).dataPicker()來將其轉換為一個日期選取器。但是,考慮一下。當一個設計師想要來檢查這個標記時,他/她能夠立刻猜出這個欄位究竟是幹什麼用的嗎?它僅僅是一個普通的input欄位還是一個日期選取器?你必須要查看jQuery來確認這點。Angular的方法是使用指令來擴充HTML。因此,一個日期選取器的指令看上去可能如下所示:
<date-picker></date-picker>
或者如下所示:
<input type='text' data-picker/>
這種建立UI成分的方法既直觀又清楚。你可以看到元素就知道它的用途。
建立自訂指令
一個Angular指令可能以四種形式出現:
1.一個新的HTML元素(<date-picker></date-picker>)
2.一個元素上的屬性(<input type='text' date-picker/>)
3.作為一個類(<input type='text' class='date-picker'/>)
4.作為注釋(<!--directive:date-picker-->)
當然,我們完全可以決定我們的指令以什麼形式出現在HTML中。現在,我們來看看一個典型的Angular指令是如何寫成的。它和controller的註冊方式類似,但是它會返回一個簡單的對象(指令定義),其中那個包含有一些配置指令的屬性。下面的代碼展示了一個簡單和Hello World指令:
var app = angular.module('myapp',[]);app.directive('helloWorld',function(){ return { restrict: 'AE', replace: true, template: '<h3>Hello World!</h3>' }});
在上面的代碼中,app.diretive()函數在我們的模組中註冊了一個新的指令。這個函數的第一個參數是指令的名稱。第二個參數是一個返回指令定義對象的函數。如果你的指令對額外的對象/服務(services)例如 $rootScope, $http 或者 $compile 有依賴,它們也可以在其中被注入。這個指令可以作為一個HTML元素來使用,如下所示:
或者:
或者作為一個屬性來使用:
或者:
如果你想要相容HTML5,你可以在屬性前面加上x-或者data-首碼。因此,下面的標記將會匹配helloWorld指令:
<div data‐hello‐world></div>
或者
<di vx‐hello‐world></div>
注意
當匹配指令時,Angular會從元素/屬性名稱之前去除首碼x-或者data-。然後將分隔字元 - 或者 : 轉換為駝峰標記法已匹配註冊的指令。這就是為什麼我們的helloWorld指令用在HTML中的時候實際上寫成了hello-world。
儘管上面的這個簡單的指令僅僅只是展示了一些靜態文本,其中還是有一些值得我們去探究的有趣的點。我們已經在這個指令定義對象中使用了三個屬性。我們來看看這三個屬性分別都有什麼用:
restrict - 這個屬性指明了一個指令應該如何在HTML中使用(記住指令可以以四種方式出現)。在這個例子中我們將它設定為'AE'。因此,這條指令可以作為一個HTML元素或者一個屬性來使用。為了允許指令作為一個類來使用我們可以將restrict設定為'AEC'。
template - 這個實行指明了當指令被Angular編譯和連結時產生的HTML標記。它不一定是一個簡單的字串。template可以很複雜,其中經常會涉及其它的指令,運算式({{}}),等等。在大多數情況下你可能會想要使用templateUrl而不是template。因此,理想情況下你應該首先將模板放置在一個單獨的HTML檔案中然後讓templateUrl指向它。
replace - 這個屬性指明了是否產生的模板會代替綁定指令的元素。在前面的例子中我們在HTML中使用指令為<hello-world></hello-world>,並將replace屬性設定為true。因此,在指令編譯後,產生的模板代替了<hello-world></hello-world>。最後的輸出結果是<h3>Hello World!</h3>。如果你將replace設定為false,預設情況下,輸出模板將會被插入到指令被調用的元素中。
link函數和範圍
有一個指令產生的模板是沒有用的除非它在正確的範圍中北編譯。預設情況下一個指令並不會得到一個新的子範圍。然而,它可以得到父範圍。這意味著如果一個指令位於在一個控制器中那麼它將使用控制器的範圍。
為了利用範圍,我們可以使用一個叫做link的函數。它可以通過指令定義對象中的link屬性來配置。我們現在對helloworld指令做一些修改一遍當使用者在一個input欄位中輸入一個顏色名稱時,Hello Wolld文字的背景顏色會自動發生改變。同樣,當一個使用者點擊Hello World文字時,背景顏色會重設為白色。相應的HTML標記如下所示:
<body ng-controller='MainCtrl'> <input type='text' ng-model='color' placeholder='Enter a color' / > <hello-wolrd/></body>
修改後的helloWorld指令代碼如下所示:
app.directive('helloWorld',function(){ return { restrict: 'AE', replace: true, template: '<p style="background-color:{{color}}"></p>', link: function(scope,elem,attr){ elem.bind('click',function(){ elem.css('background-color','white'); scope.$apply(function(){ scope.color = "white"; }); }); elem.bind('mouseover',function(){ elem.css('cursor','pointer'); }); } }});
注意到link函數被用在了指令中。它接收三個參數:
scope - 它代表指令被使用的範圍。在上面的例子中它等同於符控制器的範圍。
elem - 它代表綁定指令的元素的jQlite(jQuery的一個自己)包裹元素。如果你在AngularJS被包含之前就包括了jQuery,那麼它將變成jQuery包裹元素。由於該元素已經被jQuery/jQlite包裹,我們沒有必要將它包含在$()中來進行DOM操作。
attars - 它代表綁定指令的元素上的屬性。例如,如果你在HTML元素上有一些指令形式為:<hello-world some-attribute></hello-world>,你可以在link函數內用attrs.someAttribute來引用這些屬性。
link函數主要是用來對DOM元素繫結事件監聽器,監視模型屬性變化,並更新DOM。在前面的指令代碼中,我們綁定了兩個監聽器,click和mouseover。click處理函數重設了
的背景顏色,而mouseover處理函數則將遊標改變為pointer。模板中擁有運算式{{color}},它將隨著父範圍中的模型color的變化而變化,從而改變了Hello World的背景色。
Compile函數
Compile函數主要用來在link函數運行之前進行一些DOM轉化。它接收下面幾個參數:
tElement - 指令綁定的元素
attrs - 元素上聲明的屬性
這裡要注意compile不能夠訪問scope,而且必須返回一個link函數。但是,如果沒有compile函數以依然可以配置link函數。compile函數可以被寫成下面的樣子:
app.directive('test',function(){ return { compile: function(tElem,attrs){ //在這裡原則性的做一些DOM轉換 return function(scope,elem,attrs){ //這裡編寫link函數 } } }});
大多數時候,你僅僅只需要編寫link函數。這是因為大部分指令都只關心與註冊事件監聽器,監視器,更新DOM等等,它們在link函數中即可完成。像是ng-repeat這樣的指令,需要多次複製並重複DOM元素,就需要在link函數運行之前使用compile函數。你可能會問威懾呢麼要將兩個函數分別使用。為什麼我們不能只編寫一個函數?為了回答這個問題我們需要理解Angular是如何編譯指令的!
指令是如何被編譯的
當應用在啟動時,Angular開始使用$compile服務解析DOM。這項服務會在標記中尋找指令然後將它們各自匹配到註冊的適齡。一旦所有的指令都已經被識別完成,Angular就開始執行它們的compile函數。正如前面所提到的,compile函數返回一個link函數,該函數會被添加到稍後執行的link函數隊列中。這叫做編譯階段(compile phase)。注意到即使同一個指令有幾個執行個體存在,compile函數也只會運行一次。
在編譯階段之後就到了連結階段(link phase),這時link函數就一個接一個的執行。在這個階段中模板被產生,指令被運用到正確的範圍,DOM元素上開始有了事件監聽器。不像是compile函數,lin函數會對每個指令的執行個體都執行一次。
改變指令的範圍
預設情況下指令應該訪問父範圍。但是我們並不像對所有情況一概而論。如果我們對指令暴露了父控制器的scope,那麼指令就可以自由的修改scope屬性。在一些情況下你的指令可能想要添加一些只有內部可以使用的屬性和函數。如果我們都在父範圍中完成,可能會汙染了父範圍。因此,我們有兩種選擇:
一個子範圍 - 這個範圍會原型繼承父範圍。
一個隔離的範圍 - 一個全新的、不繼承、獨立存在的範圍。
範圍可以由指令定義對象中的scope屬性定義。下面的例子展示了這一點:
app.directive('helloWorld',function(){ return { scope: true, //使用一個繼承父範圍的自範圍 restrict: 'AE', replace: true, template: '<h3>Hello World!</h3>' }});
上面的代碼要求Angular為指令提供一個能夠原型繼承父範圍的子組用於。另一種情形,一個隔離範圍,代碼如下所示:
app.directive('helloWorld',function(){ return { scope: {}, //使用一個全新的隔離範圍 restrict: 'AE', replace: true, template: '<h3>Hello World!</h3>' }});
上面的指令使用一個不繼承父範圍的全新隔離範圍。當你想要建立一個可重用的組件時隔離範圍是一個很好的選擇。通過隔離範圍我們確保指令是自包含的兵可以輕鬆地插入到任何HTML app中。這種做法防止了父範圍被汙染,由於它不可訪問父範圍。在我們修改後的helloWorld指令中如果你將scope設定為{},那麼代碼就不會再正常運行。它將建立一個隔離的範圍然後運算式{{color}}將無法引用隔離範圍中的屬性因此值變為undefined。
隔離範圍並不意味著你一點都不能擷取到父範圍中的屬性。有一些技巧可以使你訪問父範圍中的屬性同時監聽這些屬性的變化。我們將在後續文章中提到這種進階技巧。
希望本文所述對大家AngularJS程式設計有所協助。