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

實(shí)際產(chǎn)生的標簽結構是這樣的:

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

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

生成的HTML標簽結構如下:

和第一個(gè)例子對比,這個(gè)例子的JS和HTML代碼都略有不同,JS代碼里面多了一個(gè)transclude: true,HTML代碼里面在<hello>內部出現了子標簽。
按照我們在第一個(gè)例子中的說(shuō)法,指令的作用是把我們自定義的語(yǔ)義化標簽替換成瀏覽器能夠認識的HTML標簽。那好,如果我們自定義的標簽內部出現了子標簽,應該如何去處理呢?很顯然,transclude就是用來(lái)處理這種情況的。
對于當前這個(gè)例子,transclude的作用可以簡(jiǎn)化地理解成:把<hello>標簽替換成我們所編寫(xiě)的HTML模板,但是<hello>標簽內部的內容保持不變。
很顯然,由于我們沒(méi)有加replace:true選項,所以<hello>標簽還在,沒(méi)有被替換掉。同時(shí),通過(guò)這個(gè)例子你還還會(huì )發(fā)現一 個(gè)暗藏的屬性,那就是瀏覽器實(shí)際上非常智能,雖然它并不認識<hello>這個(gè)標簽,但是頁(yè)面沒(méi)有出錯,它只是默默地把這個(gè)標簽忽略掉了!怎 么樣?是不是碉堡了?
你可以自己在上面的JS代碼里面加上replace:true,然后再看生成的HTML結構。
JS代碼:
var appModule = angular.module('app', []);appModule.directive('hello', function() { return { restrict: 'E', template: '<span>Hi there</span>', replace: true };});appModule.controller('MyController',function($scope) { $scope.things = [1,2,3,4,5,6];});
HTML代碼:
<html ng-app='app'> <body ng-controller='MyController'> <div ng-repeat='thing in things'> {{thing}}.<hello></hello> </div> </body> <script src="../angular-1.0.3/angular.min.js"></script> <script src="CompileAndLink.js"></script></html>
呃,這個(gè)例子是用來(lái)解釋一點(diǎn)點(diǎn)理論的,所以單純看效果可能看不出個(gè)鳥(niǎo)。
如前所述,指令的本質(zhì)其實(shí)是一個(gè)替換過(guò)程。好,既然如此,Angular到底是如何進(jìn)行替換的呢?嗯嗯,這個(gè)過(guò)程分2個(gè)階段,也就是本節標題所說(shuō)的compile(編譯)和link(連接)了。
簡(jiǎn)而言之,compile階段進(jìn)行標簽解析和變換,link階段進(jìn)行數據綁定等操作。這里面更加細節的處理過(guò)程請參見(jiàn)《AngularJS》這本書(shū)中的解析,這里就不贅述了(呃,實(shí)際上是因為解釋起來(lái)很長(cháng)很麻煩,叔懶得在這兒說(shuō)了

那么,知道這件事情有什么用途呢?
比方說(shuō),你有一些事件需要綁定到某個(gè)元素上,那么你需要提供一個(gè)link函數,做法請看下一個(gè)例子。
這是《AngularJS》這本書(shū)里面提供的一個(gè)例子,但是書(shū)里面沒(méi)有給出完整的可運行代碼,所以這里給出來(lái),大家參考一下。
JS代碼:
var expanderModule=angular.module('expanderModule', [])expanderModule.directive('expander', function() { return { restrict : 'EA', replace : true, transclude : true, scope : { title : '=expanderTitle' }, template : '<div>' + '<div class="title" ng-click="toggle()">{{title}}</div>' + '<div class="body" ng-show="showMe" ng-transclude></div>' + '</div>', link : function(scope, element, attrs) { scope.showMe = false; scope.toggle = function toggle() { scope.showMe = !scope.showMe; } } }});expanderModule.controller('SomeController',function($scope) { $scope.title = '點(diǎn)擊展開(kāi)'; $scope.text = '這里是內部的內容。';});
HTML代碼:
<html ng-app='expanderModule'> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <script src="../angular-1.0.3/angular.min.js"></script> <link rel="stylesheet" type="text/css" href="ExpanderSimple.css"/> </head> <body> <div ng-controller='SomeController'> <expander class='expander' expander-title='title'> {{text}} </expander> </div> </body> <script src="ExpanderSimple.js"></script></html>
CSS代碼:
.expander { border: 1px solid black; width: 250px;}.expander>.title { background-color: black; color: white; padding: .1em .3em; cursor: pointer;}.expander>.body { padding: .1em .3em;}
運行效果如下:

注意一下JS代碼里面的這一段:
link : function(scope, element, attrs) { scope.showMe = false; scope.toggle = function toggle() { scope.showMe = !scope.showMe; }}
自己跑一跑例子,研究一番,不多解釋。
JS代碼:
var expModule=angular.module('expanderModule',[])expModule.directive('accordion', function() { return { restrict : 'EA', replace : true, transclude : true, template : '<div ng-transclude></div>', controller : function() { var expanders = []; this.gotOpened = function(selectedExpander) { angular.forEach(expanders, function(expander) { if (selectedExpander != expander) { expander.showMe = false; } }); } this.addExpander = function(expander) { expanders.push(expander); } } }});expModule.directive('expander', function() { return { restrict : 'EA', replace : true, transclude : true, require : '^?accordion', scope : { title : '=expanderTitle' }, template : '<div>' + '<div class="title" ng-click="toggle()">{{title}}</div>' + '<div class="body" ng-show="showMe" ng-transclude></div>' + '</div>', link : function(scope, element, attrs, accordionController) { scope.showMe = false; accordionController.addExpander(scope); scope.toggle = function toggle() { scope.showMe = !scope.showMe; accordionController.gotOpened(scope); } } }});expModule.controller("SomeController",function($scope) { $scope.expanders = [{ title : 'Click me to expand', text : 'Hi there folks, I am the content that was hidden but is now shown.' }, { title : 'Click this', text : 'I am even better text than you have seen previously' }, { title : 'Test', text : 'test' }];});
HTML代碼:
<html ng-app="expanderModule"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <script src="../angular-1.0.3/angular.min.js"></script> <link rel="stylesheet" type="text/css" href="Accordion.css"/> </head> <body ng-controller='SomeController' > <accordion> <expander class='expander' ng-repeat='expander in expanders' expander-title='expander.title'> {{expander.text}} </expander> </accordion> </body> <script src="Accordion.js"></script></html>
CSS代碼:
.expander { border: 1px solid black; width: 250px;}.expander>.title { background-color: black; color: white; padding: .1em .3em; cursor: pointer;}.expander>.body { padding: .1em .3em;}
運行效果:

這個(gè)例子主要的難點(diǎn)在于如何在子Expander里面訪(fǎng)問(wèn)外層Accordion的scope中的數據,這一點(diǎn)解釋起來(lái)略復雜,這里就不展開(kāi)了,詳細描述參見(jiàn)《AngularJS》一書(shū)

AngularJS官方站點(diǎn)在這里:http://angularjs.org/
[全文完]
聯(lián)系客服