原版地址:http://code.angularjs.org/1.0.2/docs/guide/directivejavascript
Directive是教HTML玩一些新把戲的途徑。在DOM編譯期間,directives匹配HTML並執行。這容許directive註冊行爲或者轉換DOM結構。css
Angular自帶一組內置的directive,對於創建Web App有很大幫助。繼續擴展的話,能夠在HTML定義領域特定語言(domain specific language ,DSL)。html
1、在HTML中引用directivesjava
Directive有駝峯式(camel cased)的風格的命名,如ngBind(放在屬性裏貌似用不了~)。但directive也能夠支蛇底式的命名(snake case),須要經過:(冒號)、-(減號)或_(下劃線)鏈接。做爲一個可選項,directive能夠用「x-」或者「data-」做爲前綴,以知足HTML驗證須要。這裏列出directive的合法命名:angularjs
Directive能夠放置於元素名、屬性、class、註釋中。下面是引用myDir這個directive的等價方式。(但不少directive都限制爲「屬性」的使用方式)chrome
<span my-dir="exp"></span> <span class="my-dir: exp;"></span> <my-dir></my-dir> <!-- directive: my-dir exp -->
Directive能夠經過多種方式引用,下面列出N種等價的方式:express
<!DOCTYPE HTML> <html lang="zh-cn" ng-app> <head> <meta charset="UTF-8"> <title>invoke-directive</title> <style type="text/css"> .ng-cloak { display: none; } </style> </head> <body> <div ng-controller="MyCtrl"> Hello <input ng-model="name"/><hr/> ngBind="name" 這個用不了~~ <span ngBind="name"></span><br/> ng:bind="name"<span ng:bind="name"></span><br/> ng_bind="name"<span ng_bind="name"></span><br/> ng-bind="name"<span ng-bind="name"></span><br/> data-ng-bind="name"<span data-ng-bind="name"></span><br/> x-ng-bind="name"<span x-ng-bind="name"></span><br/> </div> <script src="../angular-1.0.1.js" type="text/javascript"></script> <script type="text/javascript"> function MyCtrl($scope) { $scope.name = "beauty~~"; } </script> </body> </html>
2、String interpolationapi
在編譯過程當中,compiler經過$interpolate服務匹配文本與屬性中的嵌入表達式(如{{something}})。這些表達式將會註冊爲watches,而且做爲digest cycle(以前不是digest-loop嗎?!)的一部分,一同更新。下面是一個簡單的interpolation:數組
<img src="img/{{username}}.jpg"/>Hello {{username}}!
3、Compilation process, and directive matching瀏覽器
HTML「編譯」的三個步驟:
1. 首先,經過瀏覽器的標準API,將HTML轉換爲DOM對象。這是很重要的一步。由於模版必須是可解析(符合規範)的HTML。這裏能夠跟大多數的模版系統作對比,它們通常是基於字符串的,而不是基於DOM元素的。
2. 對DOM的編譯(compilation)是經過調用$comple()方法完成的。這個方法遍歷DOM,對directive進行匹配。若是匹配成功,那麼它將與對應的DOM一塊兒,加入到directive列表中。只要全部與指定DOM關聯的directive被識別出來,他們將按照優先級排序,並按照這個順序執行他們的compile() 函數。directive的編譯函數(compile function),擁有一個修改DOM結構的機會,並負責產生link() function的解析。$compile()方法返回一個組合的linking function,是全部directive自身的compile function返回的linking function的集合。
3. 經過上一步返回的linking function,將模版與scope鏈接起來。這反過來會調用directive自身的linking function,容許它們在元素上註冊一些監聽器(listener),以及與scope一塊兒創建一些watches。這樣得出的結果,是在scope與DOM之間的一個雙向、即時的綁定。scope發生改變時,DOM會獲得對應的響應。
var $compile = ...; // injected into your code var scope = ...; var html = '<div ng-bind='exp'></div>'; // Step 1: parse HTML into DOM element var template = angular.element(html); // Step 2: compile the template var linkFn = $compile(template); // Step 3: link the compiled template with the scope. linkFn(scope);
4、Reasons behind the compile/link separation
在這個時候,你可能會想知道爲何編譯進程會劃分爲compile和linke兩個步驟。爲了明白這一點,讓咱們看看一個真實的例子(repeater)
Hello {{user}}, you have these actions: <ul> <li ng-repeat="action in user.actions">{{action.description}}</li> </ul>
當上面的例子被編譯時,編譯器會遍歷全部節點以尋找directive。{{user}}是一個interpolation directive的例子。ngRepeat又是另一個directive。但ngRepeat有一個難點。它須要可以很快地爲每個在users.actions中的action製造出新的li的能力。這意味着它爲了知足克隆li而且嵌入特定的action(這裏是指user的actions的其中一個值)的目的,須要保持一個乾淨li元素的拷貝,li元素須要被克隆和插入ul元素。但僅僅克隆li元素是不夠的。還須要編譯li,以便它的directive({{action.descriptions}})可以在正確的scope中被解析。原始的方法,通常會簡單地插入一個li元素的拷貝,而後編譯它。但編譯每個li元素的拷貝會比較緩慢,由於編譯過程須要咱們遍歷DOM節點樹,查找directive並運行它們。若是咱們有一個編譯,須要經過repeater對100個item進行處理,那麼咱們將陷入性能問題。
問題的解決方案,是將編譯過程分解爲兩個步驟。compile階段識別出全部directive,而且將它們按照優先級進行排序,在linking階段將特定的scope與特定的li綁定在一塊兒。
ngRepeat將各個li分開編譯以防止編譯過程落入li元素中。li元素的編譯結果是一個包含全部包含在li元素中的directive的linking function,準備與特定li元素的拷貝進行鏈接。在運行時,ngRepeat監測表達式,並做爲一個item,被加入到一個li元素拷貝的數組,爲克隆好的li元素建立新的scope,並調用該拷貝對應的link function。
總結:
5、寫一個directive(簡易版)
在這個例子裏面,咱們將創建一個根據輸入格式,顯示當前時間的directive。
<!DOCTYPE HTML> <html lang="zh-cn" ng-app="TimeFormat"> <head> <meta charset="UTF-8"> <title>time-format</title> </head> <body> <div ng-controller="MyCtrl" id="main"> Date format: <input ng-model="format" type="text"/><hr/> <!--下面使用屬性x-current-time,是爲了試試上面說的合法命名~~current:time、current-time、current_time、data-current-time -_-!!! --> Current time is : <span x-current-time="format" id="myFormat"></span><br/> <button ng-click="remove()">remove the span</button> </div> <script src="../angular-1.0.1.js" type="text/javascript"></script> <script type="text/javascript"> angular.module("TimeFormat", []) //在TimeFormat應用中註冊「currentTime」這個directive的工廠方法 //前文提到過,依賴注入,能夠直接在function的參數中寫入,這裏注入了$timeout、dataFilter .directive("currentTime", function (dateFilter) { //這個是上面提到的linking function。(不須要添加compile function,爲啥?。。。) return function (scope, element, attr) { var intervalId; //更新對應element的text值,即更新時間 function updateTime() { element.text(dateFilter(new Date(), scope.format)); } //經過watch,監控span對象的currentTime的值(是format這個model值,即input的值!!) //這個方法僅僅在format發生改變的時候執行 scope.$watch(attr.currentTime, function (value) { scope.format = value; updateTime(); }); //當span被去掉的時候,取消更新 element.bind("$destroy", function () { clearInterval(intervalId); }); intervalId = setInterval(updateTime, 1000); }; }).controller("MyCtrl",function($scope,$rootScope) { $scope.format = "M/d/yy h:mm:ss a"; $scope.remove = function() { var oFormat = document.getElementById("myFormat"); if(oFormat) { angular.element(oFormat).remove();//經過這種方式調用remove,能夠觸發$destroy事件啊!!!試了我N久。。。 } }; }); </script> </body> </html>
6、寫一個directive(詳細版)
下面是一個建立directive樣例(directive對象定義模版)。想看詳細列表,請繼續往下看。
var myModule = angular.module(...); myModule.directive('directiveName', function factory(injectables) { var directiveDefinitionObject = { priority: 0, template: '<div></div>', templateUrl: 'directive.html', replace: false, transclude: false, restrict: 'A', scope: false, compile: function compile(tElement, tAttrs, transclude) { return { pre: function preLink(scope, iElement, iAttrs, controller) { ... }, post: function postLink(scope, iElement, iAttrs, controller) { ... } } }, link: function postLink(scope, iElement, iAttrs) { ... } }; return directiveDefinitionObject; });
在大多數場景下,咱們並不須要精確控制,因此上面的定義是能夠化簡的。定義模版中的每一部分,將在下面章節講解。在這個章節,咱們僅僅關注定義模版的異構體(isomers of this skeleton,沒看懂。。。期待你們補充)。
簡化代碼的第一步是依賴默認值。所以,上面的代碼能夠簡化爲:
var myModule = angular.module(...); myModule.directive('directiveName', function factory(injectables) { var directiveDefinitionObject = { compile: function compile(tElement, tAttrs) { return function postLink(scope, iElement, iAttrs) { ... } } }; return directiveDefinitionObject; });
大多數directive只關心實例,而不是模版轉換,因此能夠進一步化簡(翻譯得很勉強。。。期待你們補充):
var myModule = angular.module(...); myModule.directive('directiveName', function factory(injectables) { return function postLink(scope, iElement, iAttrs) { ... } });
7、工廠方法
工廠方法負責建立directive。它僅僅使用一次,就在compiler第一次匹配到directive的時候。你能夠在這裏執行一些初始化操做。工廠方法經過$injector.invoke執行,讓它遵照全部注入聲明規則(rules of injection annotation),讓其變爲可注入的。
8、directive定義對象說明
directive定義對象提供了compiler的結構。屬性以下:
9、Compile function
function compile ( tElement, tAttrs, transclude) { … }
compile function用於處理DOM模版的轉換。因爲大多數directive都不須要轉換模版,因此compile不會常常被使用到。須要compile function的directive,通常是那種須要轉換DOM模版的(如ngRepeat),或者是須要異步加載內容的(如ngView)。compile function擁有一下的參數:
注意:若是template被克隆過,那麼template實例和link實例不能是同一個object。爲此,在compile function中作任何除了DOM轉換之外的事情,是不安全的,這將應用到全部克隆中。特別是,DOM事件監聽器的註冊的操做,應該在linking function中進行,而不是compile function。
compile function 能夠有一個返回值,類型能夠是function或者object。
10、Linking function
function link( scope, iElement, iAttrs, controller) { … }
link function負責註冊DOM事件監聽器,也能夠進行DOM的更新操做。link function會在模版克隆操做完畢以後執行。這裏存放着directive大多數的邏輯。
Pre-link function
在子元素linked以前執行。在這裏作DOM轉換是不安全的,由於compiler的linking function在link時可能會定位不到正確的元素。
Post-link function
在子元素linked以後執行。在這裏執行DOM轉換是安全的。
11、Attributes
attribute object - 在link()或compile()中做爲參數 - 是一個訪問如下東東的途徑:
<!DOCTYPE HTML> <html lang="zh-cn" ng-app="DirectiveProperty"> <head> <meta charset="UTF-8"> <title>directive-attribute-test</title> <style type="text/css"> .ng-cloak { display: none; } </style> </head> <body ng-controller="MyCtrl"> <input type="text" ng-model="name" value="myName"/> <p my-attr="123" directive-p2 attr-dd="{{name}}"></p> <script src="../angular-1.0.1.js" type="text/javascript"></script> <script type="text/javascript"> var app = angular.module("DirectiveProperty", []); app.controller("MyCtrl", function ($scope) { $scope.name = "my little dada"; }); var directiveP2 = app.directive("directiveP2", function () { return { link:function postLink(scope,lEle,lAttr) { console.log("myAttr:" + lAttr.myAttr);//123 console.log("myAttr:" + lAttr.attrDd);//undefinded lAttr.$observe('attrDd', function(value) { console.log('attrDd has changed value to ' + value); //my little dada //除此之外,還可啥辦法能夠拿到這個值啊。。。¥&()@#&¥(@# }); } }; }); </script> </body> </html>
12、理解transclusion和scope
咱們常常須要一些可重用的組件。下面是一段僞代碼,展現一個簡單的dialog組件如何可以工做。
<div> <button ng-click="show=true">show</button> <dialog title="Hello {{username}}." visible="show" on-cancel="show = false" on-ok="show = false; doSomething()"> Body goes here: {{username}} is {{title}}. </dialog>
下面是一段爲了dialog而編寫的模版定義:
<div ng-show="show()"> <h3>{{title}}</h3> <div class="body" ng-transclude></div> <div class="footer"> <button ng-click="onOk()">Save changes</button> <button ng-click="onCancel()">Close</button> </div> </div>
這將沒法正確渲染,除非咱們對scope作一些特殊處理。
第一個咱們須要解決的問題是,dialog模版指望title會被定義,而在初始化時會與username綁定。此外,按鈕須要onOk、onCancel兩個function出如今scope裏面。這限制了插件的效能(this limits the usefulness of the widget。。。)。爲了解決映射問題,經過以下的本地方法(locals,估計在說directive定義模版中的scope)去建立模版期待的本地變量:
scope :{ title: 'bind', // set up title to accept data-binding onOk: 'expression', // create a delegate onOk function onCancel: 'expression', // create a delegate onCancel function show: 'accessor' // create a getter/setter function for visibility. }
1. isolation(屬性隔離?) - 若是用戶忘記了在控件模版設置title這個元素屬性,那麼title將會與祖先scope的屬性」title」(若有)綁定。這是不可預知、不可取的。
2. transclusion - transcluded DOM能夠查看控件的locals(isolate scope?),locals會覆蓋transclusion中真正須要綁定的屬性。在咱們的例子中,插件中的title屬性破壞了transclusion的title屬性。
爲了解決這個缺少屬性隔離的問題,咱們須要爲這個directive定義一個isolated scope。isoloted scope不是經過從child scope(這裏爲啥是child scope?不是parent scope嗎?)原型繼承的,因此咱們無需擔憂屬性衝突問題(做爲當前scope的兄弟)。
然而,isolated scope帶來了一個新問題:若是transcluded DOM 是控件的isolated scope的一個子元素,那麼他將不能與任何東西綁定(if a transcluded DOM is a child of the widget isolated scope then it will not be able to bind to anything)。所以,transcluded scope是原始scope的一個子scope,在控件爲本地屬性而建立isolated scope以前建立的。transcluded與控件isolated scope屬於兄弟節點(scope樹中)。
這也許看起來會有點意想不到的複雜,但這樣作讓控件用戶與控件開發者至少帶來了驚喜。(解決了問題)
所以,最終的directive定義大體以下:
transclude:true, scope :{ title: 'bind', // set up title to accept data-binding onOk: 'expression', // create a delegate onOk function onCancel: 'expression', // create a delegate onCancel function show: 'accessor' // create a getter/setter function for visibility. //我試過這麼整,失敗……請繼續往下看 }
<!DOCTYPE html> <html ng-app="Dialog"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>directive-dialog</title> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <script src="../angular.js" type="text/javascript"></script> </head> <body> <div ng-controller="MyCtrl"> <button ng-click="show=true">show</button> <dialog title="Hello {{username}}" visible="{{show}}" on-cancel="show=false;" on-ok="show=false;methodInParentScope();"> <!--上面的on-cancel、on-ok,是在directive的isoloate scope中經過&引用的。若是表達式中包含函數,那麼須要將函數綁定在parent scope(當前是MyCtrl的scope)中--> Body goes here: username:{{username}} , title:{{title}}. <ul> <!--這裏還能夠這麼玩~names是parent scope的--> <li ng-repeat="name in names">{{name}}</li> </ul> </dialog> </div> <script type="text/javascript"> var myModule = angular.module("Dialog", []); myModule.controller("MyCtrl", function ($scope) { $scope.names = ["name1", "name2", "name3"]; $scope.show = false; $scope.username = "Lcllao"; $scope.title = "parent title"; $scope.methodInParentScope = function() { alert("記住。。scope裏面經過&定義的東東,是在父scope中玩的!!。。。"); }; }); myModule.directive('dialog', function factory() { return { priority:100, template:['<div ng-show="visible">', ' <h3>{{title}}</h3>', ' <div class="body" ng-transclude></div>', ' <div class="footer">', ' <button ng-click="onOk()">OK</button>', ' <button ng-click="onCancel()">Close</button>', ' </div>', '</div>'].join(""), replace:false, transclude: true, restrict:'E', scope:{ title:"@",//引用dialog標籤title屬性的值 onOk:"&",//以wrapper function形式引用dialog標籤的on-ok屬性的內容 onCancel:"&",//以wrapper function形式引用dialog標籤的on-cancel屬性的內容 visible:"@"//引用dialog標籤visible屬性的值 } }; }); </script> </body> </html>
十3、Creating Components
咱們一般會指望經過複雜的DOM結構替換掉directive(所在的元素?目的大概是使directive內部複雜點,看起來牛點@_@)。這讓directive成爲使用可複用組件的創建應用的一個快捷方式。
下面是一個可複用組件的例子:
<!DOCTYPE html> <html ng-app="ZippyModule"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>ZippyModule</title> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <style type="text/css"> .zippy { border: 1px solid black; display: inline-block; width: 250px; } .zippy.opened > .title:before { content: '▼ '; } .zippy.opened > .body { display: block; } .zippy.closed > .title:before { content: '► '; } .zippy.closed > .body { display: none; } .zippy > .title { background-color: black; color: white; padding: .1em .3em; cursor: pointer; } .zippy > .body { padding: .1em .3em; } </style> <script src="../angular.js" type="text/javascript"></script> </head> <body> <div ng-controller="MyCtrl"> Title: <input ng-model="title" type="text"><br/> Text: <textarea ng-model="text" ></textarea> <hr/> <div class="zippy" zippy-title="Details: {{title}}...">{{text}}</div> </div> <script type="text/javascript"> var myModule = angular.module("ZippyModule", []); myModule.controller("MyCtrl", function ($scope) { $scope.title = "這裏是標題"; $scope.text = "這裏是內容哇。。。"; }); myModule.directive('zippy', function () { return { template: '<div>' + ' <div class="title">{{title}}</div>' +//這個title屬於當前directive isolate scope的property ' <div class="body" ng-transclude></div>' + //這裏的東西,獲取的是父scope的property咯 '</div>', replace:true, transclude: true, restrict:'C', scope:{ title:"@zippyTitle"//綁定directive元素身上的zippy-title屬性 }, link:function(scope,element,attrs) { var title = angular.element(element.children()[0]), opened = false; title.bind("click", toogle); element.addClass("closed"); function toogle() { opened = !opened; element.removeClass(opened ? "closed" : "opened"); element.addClass(opened ? "opened" : "closed"); } } }; }); </script> </body> </html>