AngularJS 指令

指令(directives)是任何AngularJS應用中最重要的成分。儘管AngularJS已經自帶了很多指令,你經常會發現需要自己親手創建一些特別的指令。本文將會帶你瞭解自定義指令並解釋如何在現實世界中的Angular項目中使用它們。文章的最後,我們將一起用Angular指令來創建一個簡單的筆記小應用。

綜述

一個指令就是一個引入新語法的東西。指令是在DOM元素上做的標記,並同時附加了一些特定的行爲。例如,靜態的HTML並不知道如何來創建並顯示一個日期選擇插件。爲了將這個新語法教給HTML我們需要一條指令。這個指令將會創建一個充當日期選擇器的元素。我們將在隨後看到如何實現這個指令。

如果你之前已經編寫過Angular應用,那麼你已經使用過指令了,不管你有沒有意識到這點。你可能已經使用過像是ng-modelng-repeatng-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元素來使用,如下所示:

<hello-world/>   

或者:

<hello:world/>  

或者作爲一個屬性來使用:

<div hello-world></div>   

或者:

<div hello:world/>   

如果你想要兼容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。

隔離作用域並不意味着你一點都不能獲取到父作用域中的屬性。有一些技巧可以使你訪問父作用域中的屬性同時監聽這些屬性的變化。我們將在下一篇文章中提到這種高級技巧。


本文譯自A Practical Guide to AngularJS Directives,原文地址http://www.sitepoint.com/series/a-practical-guide-to-angularjs-directives/

發佈了134 篇原創文章 · 獲贊 69 · 訪問量 76萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章