本文整理並擴展了《AngularJS》這本書第六章里面的內容,此書近期即將由電子工業出版社出版,敬請期待口令:Angular
1.一點小說明
指令的作用:實現語義化標簽
我們常用的HTML標簽是這樣的:
1 <div> 2 <span>一點點內容</span> 3 </div>
而使用AngularJS的directive(指令)機制,我們可以實現這樣的東西:
1 <tabpanel> 2 <panel>子面板1</panel> 3 <panel>子面板2</panel> 4 </tabpanel>
很多人可能要驚呼,這貨和JSP或者Struts等等框架里面的taglib很像啊!
呃,說實話,實際上就是這樣的,只不過這里是使用JavaScript來實現的。正因為如此,所以很多taglib做不到的功能,使用它就都可以做到,比如訪問N層scope里面的對象之類的事情(參見后面第5個例子)。
2.實例1:從最簡單的開始
1 <html ng-app='app'> 2 <body> 3 <hello></hello> 4 </body> 5 <script src="../angular-1.0.3/angular.min.js"></script> 6 <script src="HelloDirect.js"></script> 7 </html>
對於以上代碼里面的<hello>標簽,瀏覽器顯然是不認識的,它唯一能做的事情就是無視這個標簽。那么,為了讓瀏覽器能夠認識這個標簽,我們需要使用Angular來定義一個hello指令(本質上說就是自己來把<hello>這種玩意兒替換成瀏覽器能識別的那些標准HTML標簽)。
來看這段溫馨的JS代碼:
1 var appModule = angular.module('app', []); 2 appModule.directive('hello', function() { 3 return { 4 restrict: 'E', 5 template: '<div>Hi there</div>', 6 replace: true 7 }; 8 });
以上代碼大概看兩眼就可以了,不要太在意細節。
然后我們就可以在瀏覽器里面看到這樣的內容:

實際產生的標簽結構是這樣的:

可以看到,<hello>這個東東已經被<div>Hi there</div>這個標簽替換掉了,這也是以上JS代碼里面replace:true這行配置的作用,代碼里面的template配置 項當然就是我們要的div標簽啦,至於restrict:'E'這個配置項的含義,請看下表:

ok,看完上面的表格,對於restrict這個屬性相信你已經秒懂了,那么我們來玩兒點花樣吧。如果我們需要替換的HTML標簽很長,顯然不能用 拼接字符串的方式來寫,這時候我們可以用templateUrl來替代template,從而可以把模板寫到一個獨立的HTML文件中。
3.實例2:transclude(變換)
先看例子,JS代碼:
1 var appModule = angular.module('app', []); 2 appModule.directive('hello', function() { 3 return { 4 restrict: 'E', 5 template: '<div>Hi there <span ng-transclude></span></div>', 6 transclude: true 7 }; 8 });
HTML代碼:
1 <html ng-app='app'> 2 <head> 3 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 4 </head> 5 <body> 6 <hello> 7 <br/> 8 <span>原始的內容,</span><br/> 9 <span>還會在這里。</span> 10 </hello> 11 <hello> 12 </hello> 13 </body> 14 <script src="../angular-1.0.3/angular.min.js"></script> 15 <script src="Transclude.js"></script> 16 </html>
運行效果如下:

生成的HTML標簽結構如下:

和第一個例子對比,這個例子的JS和HTML代碼都略有不同,JS代碼里面多了一個transclude: true,HTML代碼里面在<hello>內部出現了子標簽。
按照我們在第一個例子中的說法,指令的作用是把我們自定義的語義化標簽替換成瀏覽器能夠認識的HTML標簽。那好,如果我們自定義的標簽內部出現了子標簽,應該如何去處理呢?很顯然,transclude就是用來處理這種情況的。
對於當前這個例子,transclude的作用可以簡化地理解成:把<hello>標簽替換成我們所編寫的HTML模板,但是<hello>標簽內部的內容保持不變。
很顯然,由於我們沒有加replace:true選項,所以<hello>標簽還在,沒有被替換掉。同時,通過這個例子你還還會發現一 個暗藏的屬性,那就是瀏覽器實際上非常智能,雖然它並不認識<hello>這個標簽,但是頁面沒有出錯,它只是默默地把這個標簽忽略掉了!怎 么樣?是不是碉堡了?
你可以自己在上面的JS代碼里面加上replace:true,然后再看生成的HTML結構。
4.實例3:關於compile和link
JS代碼:
1 var appModule = angular.module('app', []); 2 appModule.directive('hello', function() { 3 return { 4 restrict: 'E', 5 template: '<span>Hi there</span>', 6 replace: true 7 }; 8 }); 9 appModule.controller('MyController',function($scope) { 10 $scope.things = [1,2,3,4,5,6]; 11 });
HTML代碼:
1 <html ng-app='app'> 2 <body ng-controller='MyController'> 3 <div ng-repeat='thing in things'> 4 {{thing}}.<hello></hello> 5 </div> 6 </body> 7 <script src="../angular-1.0.3/angular.min.js"></script> 8 <script src="CompileAndLink.js"></script> 9 </html>
呃,這個例子是用來解釋一點點理論的,所以單純看效果可能看不出個鳥。
如前所述,指令的本質其實是一個替換過程。好,既然如此,Angular到底是如何進行替換的呢?嗯嗯,這個過程分2個階段,也就是本節標題所說的compile(編譯)和link(連接)了。
簡而言之,compile階段進行標簽解析和變換,link階段進行數據綁定等操作。這里面更加細節的處理過程請參見《AngularJS》這本書中的解析,這里就不贅述了(呃,實際上是因為解釋起來很長很麻煩,叔懶得在這兒說了
)。
那么,知道這件事情有什么用途呢?
比方說,你有一些事件需要綁定到某個元素上,那么你需要提供一個link函數,做法請看下一個例子。
5.實例4:一個復雜一點的例子Expander
這是《AngularJS》這本書里面提供的一個例子,但是書里面沒有給出完整的可運行代碼,所以這里給出來,大家參考一下。
JS代碼:
1 var expanderModule=angular.module('expanderModule', []) 2 expanderModule.directive('expander', function() { 3 return { 4 restrict : 'EA', 5 replace : true, 6 transclude : true, 7 scope : { 8 title : '=expanderTitle' 9 }, 10 template : '<div>' 11 + '<div class="title" ng-click="toggle()">{{title}}</div>' 12 + '<div class="body" ng-show="showMe" ng-transclude></div>' 13 + '</div>', 14 link : function(scope, element, attrs) { 15 scope.showMe = false; 16 scope.toggle = function toggle() { 17 scope.showMe = !scope.showMe; 18 } 19 } 20 } 21 }); 22 expanderModule.controller('SomeController',function($scope) { 23 $scope.title = '點擊展開'; 24 $scope.text = '這里是內部的內容。'; 25 });
HTML代碼:
1 <html ng-app='expanderModule'> 2 <head> 3 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 4 <script src="../angular-1.0.3/angular.min.js"></script> 5 <link rel="stylesheet" type="text/css" href="ExpanderSimple.css"/> 6 </head> 7 <body> 8 <div ng-controller='SomeController'> 9 <expander class='expander' expander-title='title'> 10 {{text}} 11 </expander> 12 </div> 13 </body> 14 <script src="ExpanderSimple.js"></script> 15 </html>
CSS代碼:
1 .expander { 2 border: 1px solid black; 3 width: 250px; 4 } 5 .expander>.title { 6 background-color: black; 7 color: white; 8 padding: .1em .3em; 9 cursor: pointer; 10 } 11 12 .expander>.body { 13 padding: .1em .3em; 14 }
運行效果如下:

注意一下JS代碼里面的這一段:
1 link : function(scope, element, attrs) { 2 scope.showMe = false; 3 scope.toggle = function toggle() { 4 scope.showMe = !scope.showMe; 5 } 6 }
自己跑一跑例子,研究一番,不多解釋。
6.實例5:一個綜合的例子
JS代碼:
1 var expModule=angular.module('expanderModule',[]) 2 expModule.directive('accordion', function() { 3 return { 4 restrict : 'EA', 5 replace : true, 6 transclude : true, 7 template : '<div ng-transclude></div>', 8 controller : function() { 9 var expanders = []; 10 this.gotOpened = function(selectedExpander) { 11 angular.forEach(expanders, function(expander) { 12 if (selectedExpander != expander) { 13 expander.showMe = false; 14 } 15 }); 16 } 17 this.addExpander = function(expander) { 18 expanders.push(expander); 19 } 20 } 21 } 22 }); 23 expModule.directive('expander', function() { 24 return { 25 restrict : 'EA', 26 replace : true, 27 transclude : true, 28 require : '^?accordion', 29 scope : { 30 title : '=expanderTitle' 31 }, 32 template : '<div>' 33 + '<div class="title" ng-click="toggle()">{{title}}</div>' 34 + '<div class="body" ng-show="showMe" ng-transclude></div>' 35 + '</div>', 36 link : function(scope, element, attrs, accordionController) { 37 scope.showMe = false; 38 accordionController.addExpander(scope); 39 scope.toggle = function toggle() { 40 scope.showMe = !scope.showMe; 41 accordionController.gotOpened(scope); 42 } 43 } 44 } 45 }); 46 expModule.controller("SomeController",function($scope) { 47 $scope.expanders = [{ 48 title : 'Click me to expand', 49 text : 'Hi there folks, I am the content that was hidden but is now shown.' 50 }, { 51 title : 'Click this', 52 text : 'I am even better text than you have seen previously' 53 }, { 54 title : 'Test', 55 text : 'test' 56 }]; 57 });
HTML代碼:
1 <html ng-app="expanderModule"> 2 <head> 3 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 4 <script src="../angular-1.0.3/angular.min.js"></script> 5 <link rel="stylesheet" type="text/css" href="Accordion.css"/> 6 </head> 7 <body ng-controller='SomeController' > 8 <accordion> 9 <expander class='expander' ng-repeat='expander in expanders' expander-title='expander.title'> 10 {{expander.text}} 11 </expander> 12 </accordion> 13 </body> 14 <script src="Accordion.js"></script> 15 </html>
CSS代碼:
1 .expander { 2 border: 1px solid black; 3 width: 250px; 4 } 5 .expander>.title { 6 background-color: black; 7 color: white; 8 padding: .1em .3em; 9 cursor: pointer; 10 } 11 12 .expander>.body { 13 padding: .1em .3em; 14 }
運行效果:

這個例子主要的難點在於如何在子Expander里面訪問外層Accordion的scope中的數據,這一點解釋起來略復雜,這里就不展開了,詳細描述參見《AngularJS》一書 
AngularJS官方站點在這里:http://angularjs.org/
[全文完]
其它相關內容:
1、OReilly的《AngularJS》已由電子工業出版社出版
http://damoqiongqiu.iteye.com/blog/1965167
2、對比Angular/jQueryUI/Extjs:沒有一個框架是萬能的
http://damoqiongqiu.iteye.com/blog/1922004
3、AngularJS表單基礎
http://damoqiongqiu.iteye.com/blog/1920191
4、AngularJS Form 進階:遠程校驗和自定義輸入項
http://damoqiongqiu.iteye.com/blog/1920993
5、AngularJS:在Windows上安裝Yeoman
http://damoqiongqiu.iteye.com/blog/1885371
6、使用JsTestDriver實現JavaScript單元測試
