AngularJS 自定義控件
自定義指令介紹
AngularJS 指令作用是在 AngulaJS 應用中操作 Html 渲染。比如說,內插指令 ( {{ }}
), ng-repeat
指令以及 ng-if
指令。
當然你也可以實現自己的。這就是 AngularJS 所謂的"教會 HTML 玩新姿勢”。本文將告訴你如何做到。
指令類型
可以自定義的指令類型如下:
- 元素
- 屬性
- CSS class
- Comment directives </ul>
這里面,AngularJS 強烈建議你用元素和屬性類型,而不用 CSS class 和 comment directives (除非迫不得已)。
指令類型決定了指令何時被激活。當 AngularJS 在 HTML 模板中找到一個 HTML 元素的時候,元素指令被激活。當 AngularJS 找到一個 HTML 元素屬性時,屬性指令被激活。當 AngularJS 找到一個 CSS class 時, CSS class 指令被激活,最后,當 AngularJS 找到 HTML comment 時,comment directive 被激活。
基礎例子
你可以向模塊注冊一個指令,像這樣:
myapp = angular.module("myapp", []);myapp.directive('div', function() { var directive = {};
directive.restrict = 'E'; /* restrict this directive to elements */ directive.template = "My first directive: {{textToInsert}}"; return directive;
});</pre>
來看模塊中調用的
directive()
函數。當你調用該函數時,意味著你要注冊一個新指令。directive()
函數的第一個參數是新注冊指令的名稱。這是之后你在 HTML 模板中調用它的時候用的名稱。例子中,我用了'div'
,意思是說當 HTML 模板每次在找到 HTML 元素類型是div
的時候,這個指令都會被激活。傳遞給
directive
函數的第二個參數是一個工廠函數。調用該函數會返回一個指令的定義。 AngularJS 通過調用該函數來獲取包含指令定義的 Javascript 對象。如果你有仔細看上面的例子,你會知道它返回的確是是一個 Javascript 對象。這個從工廠函數中返回的 Javascript 對象有兩個屬性:
restrict
和template
字段。
restrict
字段用來設置指令何時被激活,是匹配到 HTML 元素時,還是匹配到元素屬性時。也就是指令類型。把restrict
設置為E
時,只有名為div
的 HTML 元素才會激活該指令。把restrict
設置為A
時,只有名為div
的 HTML 元素屬性才會激活該指令。你也可以用AE
,這樣會使得匹配到元素名和元素屬性名時,都可以激活該指令。
template
字段是一個 HTML 模板,用來替換匹配的div
元素。它會把匹配到的div
當成一個占位符,然后用 HTML 模板在同一位置來替換掉它。也就是說,HTML 模板替換匹配的到 HTML 元素。比如說你的 HTML 頁面有這樣一段 HTML:
<div ng-controller="MyController" > <div>This div will be replaced</div> </div>當 AngularJS 找到內嵌的
div
的時候,會激活自定義的指令。然后把該div
元素替換掉,替換后的 HTML 將變成這樣:My first directive: {{textToInsert}}然后你看到了,這段 HTML 里面包含了一個內插指令 (
{{textToInsert}}
)。AngularJS 會再執行一次,讓內插指令實際值顯示出來。然后 $scope.textToInsert 屬性將會在這個 HTML 點上替換掉內插指令占位符。template 和 templateUrl 屬性
創建自定義指令最簡單的例子就是上面這樣了。你的指令用來生成 HTML,然后你把 HTML 放到指令定義對象的
template
屬性中。下面這個例子是上面那最簡單的例子的重復,注意template
部分:myapp = angular.module("myapp", []);myapp.directive('div', function() { var directive = {};
directive.restrict = 'E'; /* restrict this directive to elements */ directive.template = "My first directive: {{textToInsert}}"; return directive;
});</pre>
如果 HTML 模板越來越大,把它寫在一個 Javascript 字符串中肯定非常難維護。你可以把它放到一個單獨的文件中,然后 AngularJS 可以通過這個文件把它加載進來。然后再把 HTML 模板文件的 URL 放到指令定義對象的
templateUrl
屬性中。下面是例子:myapp = angular.module("myapp", []);myapp.directive('div', function() { var directive = {};
directive.restrict = 'E'; /* restrict this directive to elements */ directive.templateUrl = "/myapp/html-templates/div-template.html"; return directive;
});</pre>
然后 AngularJS 會從
templateUrl
屬性中設置的 URL 將 HTML 模板加載進來。用獨立的 HTML 模板文件,設置
templateUrl
屬性,在你要創建更多的通用指令時會顯得更加有用,比如說用來顯示用戶信息的指令。例子:myapp = angular.module("myapp", []);myapp.directive('userinfo', function() { var directive = {};
directive.restrict = 'E'; /* restrict this directive to elements */ directive.templateUrl = "/myapp/html-templates/userinfo-template.html"; return directive;
});</pre>
例子創建了一個指令,當AngularJS 找到一個
<userinfo>
元素的時候就會激活它。AngularJS 加載指向/myapp/html-templates/userinfo-template.html
的模板并解析它,它就像從一開始就被嵌在上一級 HTML 文件中一樣。從指令中隔離 $scope
在上面的例子中,把
userinfo
指令硬綁定到了$scope
,因為 HTML 模板是直接引用textToInsert
屬性的。直接引用$scope
讓指令在同一個controller
中的時候,非常難復用,因為$scope
在同一個controller
中的值都是一樣的。比如說,你在頁面的 HTML 中這樣寫的時候:<userinfo></userinfo> <userinfo></userinfo>這兩個
<userinfo>
元素會被同樣的 HTML 模板取代,然后綁定到同樣的$scope
變量上。結果就是兩個<userinfo>
元素將會被一模一樣的 HTML 代碼給替換了。為了把兩個
<userinfo>
元素綁定到$scope
中的不同的值,你需要給 HTML 模板一個 isolate scope。所謂 isolate scope 是綁定在指令上的獨立的 scope 對象。下面是定義的例子:
myapp.directive('userinfo', function() { var directive = {};directive.restrict = 'E'; directive.template = "User : {{user.firstName}} {{user.lastName}}"; directive.scope = { user : "=user" } return directive;
})</pre>
請看 HTMl 模板中的兩個內插指令
{{user.firstName}}
和{{user.lastName}}
。注意user.
部分。以及directive.scope
屬性。directive.scope
是個帶user
屬性的 Javascript 對象。于是directive.scope
就成為了isolate scope
對象,現在 HTML 模板被綁到了directive.scope.user
上(通過{{user.firstName}}
和{{user.lastName}}
內插指令)。
directive.scope.user
被設置為 “=user” 。意思是說directive.scope.user
和scope
中的屬性綁在一起的(不是 isolate scope),scope
中的屬性通過user
屬性傳入<userinfo>
元素。聽起來挺費勁的,直接看例子:<userinfo user="jakob"></userinfo> <userinfo user="john"></userinfo>這兩個
<userinfo>
元素都有user
屬性。user
的值指向了$scope
中同名屬性,被指定的$scope
中的屬性將在userinfo
的isolate scope object
中被使用。下面是完整的例子:
<userinfo user="jakob"></userinfo> <userinfo user="john"></userinfo><script> myapp.directive('userinfo', function() { var directive = {};
directive.restrict = 'E'; directive.template = "User : <b>{{user.firstName}}</b> <b>{{user.lastName}}</b>"; directive.scope = { user : "=user" } return directive;
});
myapp.controller("MyController", function($scope, $http) { $scope.jakob = {}; $scope.jakob.firstName = "Jakob"; $scope.jakob.lastName = "Jenkov";
$scope.john = {}; $scope.john.firstName = "John"; $scope.john.lastName = "Doe";
});
</script></pre>
compile() 和 link() 函數
如果你需要在你的指令中做更高級的操作,單純使用 HTML 模板做不到的時候,你可以考慮使用
compile()
和link()
函數。
compile()
和link()
函數定義了指令如何修改匹配到的 HTML。當指令第一次被 AngularJS 編譯的時候 (在 HTML 中第一次被發現),
compile()
函數被調用。compile()
函數將為該元素做一次性配置。然后
compile()
函數以返回link()
作為終結。link()
函數將在每次元素被綁到$scope
數據時,都被調用。下面是例子:
<script> myapp = angular.module("myapp", []); myapp.directive('userinfo', function() { var directive = {};directive.restrict = 'E'; /* restrict this directive to elements */ directive.compile = function(element, attributes) { // do one-time configuration of element. var linkFunction = function($scope, element, atttributes) { // bind element to data in $scope } return linkFunction; } return directive;
});
</script></pre>`
<br />
compile()函數帶兩個參數:
element和
attributes`。
element
參數是 jqLite 包裝過的 DOM 元素。AngularJS 有一個簡化版 jQuery 可以用于操作 DOM,所以對element
的 DOM 操作方式和你在 jQuery 中所知的一樣。
attributes
參數是包含了 DOM 元素中的所有的屬性集合的 Javascript 對象。因此,你要訪問某個屬性你可以這樣寫attributes.type
。
link()
函數帶三個參數:$scope
,element
和attributes
。element
和attributes
參數和傳到compile()
函數中的一樣。$scope
參數是正常的 scope 對象,或者當你在指令定義對象中定義了 isolate scope 的時候,它就是 isolate scope。
compile()
和link()
的命名相當混亂。它們肯定是受編譯隊伍的啟發而命名的。我可以看到相似點,不過一個編譯器是傳入一次,然后輸出。而指令則是配置一次 HTML 元素,然后在之后的$scope
對象改變時進行更新。
compile()
函數如果叫做create()
,init()
或者configure()
也許會更好。這樣可以表達出這個函數只會被調用一次的意思。而
link()
函數如果叫bind()
或者render()
也許會更好,能更好的表達出這樣的意思,在指令綁定數據或者重綁定數據的時候,這個函數將會被調用。下面是一個完整的例子,演示了指令使用
compile()
和link()
函數的:<div ng-controller="MyController" > <userinfo >This will be replaced</userinfo> </div><script> myapp = angular.module("myapp", []); myapp.directive('userinfo', function() { var directive = {};
directive.restrict = 'E'; /* restrict this directive to elements */ directive.compile = function(element, attributes) { element.css("border", "1px solid #cccccc"); var linkFunction = function($scope, element, attributes) { element.html("This is the new content: " + $scope.firstName); element.css("background-color", "#ffff00"); } return linkFunction; } return directive; }) myapp.controller("MyController", function($scope, $http) { $scope.cssClass = "notificationDiv"; $scope.firstName = "Jakob"; $scope.doClick = function() { console.log("doClick() called"); } });
</script></pre>
compile()
函數設置 HTML 元素的 border 。它只執行一次,因為compile()
函數只執行一次。
link()
替換 HTML 元素內容,并且把背景顏色設置為黃色。把設置 border 放在
compile()
, 把背景顏色放在link()
沒啥特別的理由。你可以把所有的操作都丟到compile()
,或者都放到link()
。如果都放到compile()
它們只會被設置一次(你需要它們是常量的話)。如果放到了link()
,它們會在每次 HTML 元素被綁到$scope
的時候都發生變化。當你希望根據$scope
中的數據來設置 boarder 和背景顏色的時候這非常有用。只設置 link() 函數
有時候你的指令可能不需要
compile()
。你只需要用到link()
。這種情況下,你可以直接設置指令定義對象中的link()
函數。下面是一個對上面例子的修改,只用 link 函數:<div ng-controller="MyController" > <userinfo >This will be replaced</userinfo> </div><script> myapp = angular.module("myapp", []); myapp.directive('userinfo', function() { var directive = {};
directive.restrict = 'E'; /* restrict this directive to elements */ directive.link = function($scope, element, attributes) { element.html("This is the new content: " + $scope.firstName); element.css("background-color", "#ffff00"); } return directive; }) myapp.controller("MyController", function($scope, $http) { $scope.cssClass = "notificationDiv"; $scope.firstName = "Jakob"; $scope.doClick = function() { console.log("doClick() called"); } });
</script></pre>
注意
link()
方法和之前例子中返回的link()
是完全一樣的。通過 Transclusion 封裝元素的指令
到目前為止,我們看到的所有例子,都是把匹配到的元素內容給替換為指令的指定內容,不管是通過 Javascript 也好或者 HTML 模板也好。不過如果遇到內容中有部分是開發者可以指定的內容的時候呢?比如說:
<mytransclude>This is a transcluded directive {{firstName}}</mytransclude>標記為
<mytransclude>
的元素,它的部分內容可以由開發者設置。因此,這部分 HTML 不應該被指令的 HTML 模板給替換。我們實際上是希望這部分 HTML 由 AngularJS 來處理的。這個處理叫做 “transclusion”。 1為了能讓 AngularJS 把這部分 HTML 放到指令內部處理,你必須設置指令定義對象的
transclude
屬性為true
。你還需要告訴 AngularJS,指令的 HTML 模板中哪一部分需要把 transcluded HTML 包含進來。你可以通過插入ng-transclude
屬性 (實際上,是一個指令) 到 HTML 模板的 HTML 元素中,標記你想要添加 transcluded HTML 的元素。下面是一個 AngularJS 指令,演示如何使用 transclusion:
<mytransclude>This is a transcluded directive {{firstName}}</mytransclude><script> myapp = angular.module("myapp", []); myapp.directive('mytransclude', function() { var directive = {};
directive.restrict = 'E'; /* restrict this directive to elements */ directive.transclude = true; directive.template = "<div class='myTransclude' ng-transclude></div>"; return directive; }); myapp.controller("MyController", function($scope, $http) { $scope.firstName = "Jakob"; });
</script></pre>
注意
<mytransclude>
元素內的 HTML。這部分 HTML 代碼包含了內插指令{{firstName}}
。我們希望 AngularJS 來為我們處理這部分 HTML,讓內插指令執行。為了實現這個目的,我在指令定義對象中把transclude
設置為true
。我還在 HTML 模板中用到了ng-transclude
屬性。這個屬性告訴 AngularJS 什么元素需要插入到 transcluded HTML。
1: 說實話,我沒看懂那個定義,說的太TM難懂了,而且我好不爽寫代碼沒輸出的教程。只好自己動手做做例子。我覺得其實應該是這樣的,把目標元素內容作為一個整體,拿到 HTML 模板中來,添加到 ng-transclude 指定的標簽下。這個處理,我覺得應該就是叫做 transcluded。比如說剛才的例子(有些 bug,自己修正一下),因為
directive.transclude = true;
,所以原來<mytransclude>
元素內的 HTML:This is a transcluded directive {{firstName}}在激活指令 'mytransclude' 的時候,會被拿到 'mytransclude' 指令的模板中來,放到被
ng-transclude
指定的"<div class='myTransclude' ng-transclude></div>"中。于是最終輸出的結果應該是:
<mytransclude> <div class='myTransclude' ng-transclude> <span class="ng-scope ng-binding">This is a transcluded directive Jakob</span> </div> </mytransclude>來自:http://my.oschina.net/ilivebox/blog/289670