(編輯完這篇以後,發現本篇內容應該屬於AngularJS的進階,內容有點多,有幾個例子偷懶直接用了官方的Demo稍加了一些註釋,敬請見諒)。javascript
前面一篇介紹了各類經常使用的AngularJS內建的Directives以及對應的代碼實例。這篇咱們再看看如何建立本身的Directive吧!css
何時須要自定義Directive?html
使你的Html更具語義化,不須要深刻研究代碼和邏輯便可知道頁面的大體邏輯。java
抽象一個自定義組件,在其餘地方進行重用。app
看一下以下2個代碼片斷:ide
示例1:ui
1 <body> 2 <div> 3 <p>This is your class name.</p> 4 <div> 5 <p>Your teacher:</p> 6 <p>Mr. Wang</p> 7 <p>35 years old</p> 8 <p>English</p> 9 <p>Descriptions: 1.85cm tall, with a pair of brown glasses, unmarried, easy going etc.</p> 10 </div> 11 <div> 12 <div> 13 <p>Students in the class:</p> 14 <div> 15 <p>Jack</p> 16 <p>Male</p> 17 <p>15</p> 18 <p>Description: Smart ...</p> 19 </div> 20 <div> 21 <p>May</p> 22 <p>Female</p> 23 <p>14</p> 24 <p>Description: Diligent ...</p> 25 </div> 26 <div> 27 <p>Tom</p> 28 <p>Male</p> 29 <p>15</p> 30 <p>Description: Naughty ...</p> 31 </div> 32 <div> 33 <p>Alice</p> 34 <p>Female</p> 35 <p>14</p> 36 <p>Description: Smart ...</p> 37 </div> 38 </div> 39 </div> 40 </div> 41 </body>
示例2:this
1 <body ng-app> 2 <class-info> 3 <teacher-info></teacher-info> 4 <student-infos></student-infos> 5 </class-info> 6 </body>
示例1中的代碼你可能要完整的看完才能知道邏輯(固然示例1也不復雜,你能夠想象下真實的場景要比這個複雜的多的多),不是說示例2中的代碼少(邏輯被轉移到其餘地方去了),而是在示例2中,光看Html標籤就知道這個頁面是在展現班級信息,班級信息中還有班主任的信息和全部學生的信息。spa
另外,示例1中,若一個班級的學生有30個,學生信息的Html會出現30次,若是未來發生變更,這30出學生信息的代碼都須要改動。rest
製做一個屬於本身的Directive
示例3:
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('ngCustomDirectiveTest', []); 8 app.controller('myController', ['$scope', function ($scope) { 9 $scope.info = { 10 yourname: 'Jack', 11 template: 'template.html' 12 }; 13 }]); 14 15 // 自定義Element的Directive 16 app.directive("studentInfo", function () { 17 return { 18 // A 表明 Attribute 19 // C 表明 Class 20 // E 表明 Element 21 // ACE 表示同時建立 A、C、E 三種 22 restrict: 'ACE', 23 // templateUrl 指向獨立的Html文件,AngularJS會用Html文件中的內容替換studentInfo對象 24 templateUrl: 'template.html' 25 }; 26 }); 27 })(); 28 </script> 29 </head> 30 <body ng-app="ngCustomDirectiveTest"> 31 <div ng-controller="myController as myCtrl"> 32 <student-info></student-info> 33 <br /> 34 <data-student-info></data-student-info> 35 <br /> 36 37 <div student-info></div> 38 <br /> 39 <div data_student-info></div> 40 <br /> 41 42 <div class="student-info"></div> 43 <br /> 44 <div class="data-student-info"></div> 45 <br /> 46 </div> 47 </body> 48 </html>
template.html:
<div> <p>This is a custom template.</p> <p>Your name: {{info.yourname}}</p> </div>
注意:你可能還見過restrict:'M',或者Directive的命名以pre_suf、pre:suf這樣的代碼書寫方式,這些都已經「過期」了,最潮的restrict僅使用ACE三種,命名方式使用pre-suf。
另外,你可能疑惑,爲何加上"data-"前綴的爲何也能被解析?實際上AngularJS在處理Directive時,首先會忽略Directive命名中的"data-"或者"x-"前綴,所以不管你加上"data-"仍是"x-",AngularJS仍是能正確解析的,不過"x-"也是一種過期的寫法,咱們能夠忽略。
好了,是否是很容易?屬於咱們本身的Directive就這樣建立成功了,接着讓咱們更深刻一些,看一下Directive的scope屬性。首先看一下如下3段代碼:
示例4(student-info直接使用了包含它的Controller的Scope中的變量jack和alice):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('ngCustomDirectiveTest', []); 8 app.controller('myController', ['$scope', function ($scope) { 9 $scope.jack = { 10 name: 'Jack', 11 sex: 'Male' 12 }, 13 $scope.alice = { 14 name: 'Alice', 15 sex: 'Female' 16 } 17 }]); 18 19 app.directive("studentInfo", function () { 20 return { 21 restrict: 'E', 22 template: '<div><p>Student name: {{jack.name}}</p><p>Student sex: {{jack.sex}}</p></div><br /><div><p>Student name: {{alice.name}}</p><p>Student sex: {{alice.sex}}</p></div>' 23 }; 24 }); 25 })(); 26 </script> 27 </head> 28 <body ng-app="ngCustomDirectiveTest"> 29 <div ng-controller="myController as myCtrl"> 30 <student-info></student-info> 31 </div> 32 </body> 33 </html>
示例5(和示例1相似,直接使用包含student-info的Controller中的變量students,在template中使用ng-repeat展現學生信息):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('ngCustomDirectiveTest', []); 8 app.controller('myController', ['$scope', function ($scope) { 9 $scope.students = [ 10 { 11 name: 'Jack', 12 sex: 'Male' 13 }, 14 { 15 name: 'Alice', 16 sex: 'Female' 17 } 18 ]; 19 }]); 20 21 app.directive("studentInfo", function () { 22 return { 23 restrict: 'E', 24 template: '<div ng-repeat="stu in students"><p>Student name:{{stu.name}}</p><p>Student sex:{{stu.sex}}</p></div>' 25 }; 26 }); 27 })(); 28 </script> 29 </head> 30 <body ng-app="ngCustomDirectiveTest"> 31 <div ng-controller="myController as myCtrl"> 32 <student-info></student-info> 33 </div> 34 </body> 35 </html>
示例6(定義兩個不一樣的Controller:jackController和aliceController,使student-info處於2個不一樣的controller中):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('ngCustomDirectiveTest', []); 8 app.controller('jackController', ['$scope', function ($scope) { 9 $scope.student = 10 { 11 name: 'Jack', 12 sex: 'Male' 13 } 14 }]); 15 16 app.controller('aliceController', ['$scope', function ($scope) { 17 $scope.student = 18 { 19 name: 'Alice', 20 sex: 'Female' 21 } 22 }]); 23 24 app.directive("studentInfo", function () { 25 return { 26 restrict: 'E', 27 template: '<div><p>Student name:{{student.name}}</p><p>Student sex:{{student.sex}}</p></div>' 28 }; 29 }); 30 })(); 31 </script> 32 </head> 33 <body ng-app="ngCustomDirectiveTest"> 34 <div ng-controller="jackController as jackCtrl"> 35 <student-info></student-info> 36 </div> 37 <br /> 38 <div ng-controller="aliceController as aliceCtrl"> 39 <student-info></student-info> 40 </div> 41 </body> 42 </html>
上述三種方式,都能達到咱們所需的目的:自定義一個名爲student-info的Directive,展現Controller中的學生信息。但仔細分析上述3種不一樣的代碼,能發現它們各自有不一樣的問題:
示例4中,student-info的template中的全部表達式嚴重依賴Controller中的變量定義,致使student-info沒法抽象成一個公共的學生信息展現模塊。
示例5中,雖然使用ng-repeat封裝了代碼,可是仍是存在依賴Controller中students變量的問題,示例5僅比示例4稍微好點。
示例6中,定義了不一樣的Controller來隔離做用域,但N個學生須要定義N個做用域,而且定義Controller時,仍是必須定義一個名爲student的變量,不然代碼沒法正確執行,所以仍是存在耦合性。
好吧,讓咱們看看AngularJS爲咱們提供的優雅的解決方案-Isolate scope:
示例7(經過 使用 =attr 將Isolate scope中的屬性賦值給Directive的名爲'attr'的Attribute):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('ngCustomDirectiveTest', []); 8 app.controller('myController', ['$scope', function ($scope) { 9 $scope.jack = { 10 name: 'Jack', 11 sex: 'Male' 12 }, 13 $scope.alice = { 14 name: 'Alice', 15 sex: 'Female' 16 } 17 }]); 18 19 app.directive("studentInfo", function () { 20 return { 21 restrict: 'E', 22 // 定義student-info的Isolate scope 23 scope: { 24 // 做用域內定義一個變量:newNameInScope 25 // 值對應到Directive中的info屬性 26 newNameInScope: '=info' 27 }, 28 // template 再也不依賴外部, 僅依賴內部的newNameInScope變量 29 template: '<div><p>Student name: {{newNameInScope.name}}</p><p>Student sex: {{newNameInScope.sex}}</p></div>' 30 }; 31 }); 32 })(); 33 </script> 34 </head> 35 <body ng-app="ngCustomDirectiveTest"> 36 <div ng-controller="myController as myCtrl"> 37 <!--將myController中的jack屬性傳遞給info--> 38 <student-info info="jack"></student-info> 39 <br /> 40 <!--將myController中的alice屬性傳遞給info--> 41 <student-info info="alice"></student-info> 42 </div> 43 </body> 44 </html>
不一樣之處已經在註釋中說明,示例7已經徹底將student-info與外界隔離,不在存在耦合性,真正達到了咱們自定義Directive的目的2(見本文"何時須要自定義Directive"部分)。
讓咱們再對示例7進行一些調整:
示例8:
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('ngCustomDirectiveTest', []); 8 app.controller('myController', ['$scope', function ($scope) { 9 $scope.jack = { 10 name: 'Jack', 11 sex: 'Male' 12 }, 13 $scope.alice = { 14 name: 'Alice', 15 sex: 'Female' 16 } 17 }]); 18 19 app.directive("studentInfo", function () { 20 return { 21 restrict: 'E', 22 scope: { 23 newNameInScope: '=info' 24 }, 25 // 這裏的alice將不能獲取Controller中的變量alice的信息 26 template: '<div><p>Student name: {{newNameInScope.name}}</p><p>Student sex: {{newNameInScope.sex}}</p><br /><p>Deskmate name: {{alice.name}}</p><p>Deskmate sex: {{alice.sex}}</p></div>' 27 }; 28 }); 29 })(); 30 </script> 31 </head> 32 <body ng-app="ngCustomDirectiveTest"> 33 <div ng-controller="myController as myCtrl"> 34 <student-info info="jack"></student-info> 35 </div> 36 </body> 37 </html>
這個就是所謂的封閉(Isolate),對比一下示例4,當建立student-info時指定了scope屬性後,不在scope中指定的變量,在student-info中將沒法被識別,作到了「封閉」。這樣,當你定義一個公共模塊時,不會由於在不一樣的Controller中使用而產生意想不到的問題。所以當你須要定義一個具備隔離性的Directive時,即便不須要傳遞Controller中的變量,也務必加上scope屬性。
不過咱們只能將一個字符串或者一個對象傳入Isolate scope中,試想若遇到某些特殊狀況,須要直接包含指定的Html片斷時怎麼辦?AngularJS也是有這樣的功能的。
示例9:
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('ngCustomDirectiveTest', []); 8 app.controller('myController', ['$scope', function ($scope) { 9 $scope.jack = { 10 name: 'Jack', 11 sex: 'Male' 12 }, 13 $scope.alice = { 14 name: 'Alice', 15 sex: 'Female' 16 } 17 }]); 18 19 app.directive("studentInfo", function () { 20 return { 21 restrict: 'E', 22 // 指定transclude屬性爲true 23 transclude: true 24 }; 25 }); 26 })(); 27 </script> 28 </head> 29 <body ng-app="ngCustomDirectiveTest"> 30 <div ng-controller="myController as myCtrl"> 31 <!--指明student-info將會使用transclude模式--> 32 <student-info ng-transclude> 33 <!-- student-info的內容由使用者本身指定,而且內容中能訪問student-info的scope之外的變量 --> 34 <p>Student name: {{jack.name}}</p> 35 <p>Student sex: {{jack.sex}}</p> 36 <br /> 37 <p>Deskmate name: {{alice.name}}</p> 38 <p>Deskmate sex: {{alice.sex}} 39 </student-info> 40 </div> 41 </body> 42 </html>
其餘自定義Directive的示例
示例10(自定義Directive操做DOM,官方文檔中的demo):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('docsTimeDirective', []); 8 9 app.controller('Controller', ['$scope', function ($scope) { 10 $scope.format = 'M/d/yy h:mm:ss a'; 11 }]) 12 13 app.directive('myCurrentTime', ['$interval', 'dateFilter', function ($interval, dateFilter) { 14 function link(scope, element, attrs) { 15 var format, 16 timeoutId; 17 18 function updateTime() { 19 element.text(dateFilter(new Date(), format)); 20 } 21 22 scope.$watch(attrs.myCurrentTime, function (value) { 23 format = value; 24 updateTime(); 25 }); 26 27 element.on('$destroy', function () { 28 $interval.cancel(timeoutId); 29 }); 30 31 timeoutId = $interval(function () { 32 updateTime(); 33 }, 1000); 34 } 35 36 return { 37 link: link 38 }; 39 }]); 40 })(); 41 </script> 42 </head> 43 <body ng-app="docsTimeDirective"> 44 <div ng-controller="Controller"> 45 Date format: 46 <input ng-model="format"> 47 <hr /> 48 Current time is: <span my-current-time="format"></span> 49 </div> 50 </body> 51 </html>
若是想要使Directive改變DOM,通常會用到link參數,其原型爲:function link(scope, element, attrs) {...}:
scope: 與當前元素結合的scope
elment:當前元素
$attrs:當前元素的屬性對象
示例11(經過使用&attr開放Directive,將自定義的方法綁定到Directive上):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('isoFnBindTest', []); 8 9 app.controller('myController', ['$scope', function ($scope) { 10 $scope.name = ''; 11 $scope.message = ''; 12 $scope.isHide = true; 13 $scope.sayHello = function (message, name) { 14 $scope.isHide = false; 15 $scope.name = name; 16 $scope.message = message; 17 alert($scope.message + ',' + $scope.name); 18 }; 19 }]); 20 21 app.directive('myGreeting', function () { 22 return { 23 restrict: 'E', 24 transclude: true, 25 scope: { 26 // Step 2: greet方法綁定到onGreet屬性(對應Html中的on-greet),並將greet的輸入參數傳給onGreet 27 'greet': '&onGreet' 28 }, 29 templateUrl: 'my-greeting.html' 30 }; 31 }); 32 })(); 33 </script> 34 </head> 35 <body ng-app="isoFnBindTest"> 36 <div ng-controller="myController"> 37 <!-- Step 3: on-greet指向了myController中的sayHello方法,此時on-greet中能直接訪問到greet的輸入參數--> 38 <my-greeting on-greet="sayHello(message, name)"> 39 <div ng-hide="isHide"> 40 {{message}}, {{name}}! 41 </div> 42 </my-greeting> 43 </div> 44 </body> 45 </html>
my-greeting.html:
1 <div> 2 <!-- Step1: 一旦觸發click, 將調用Isolate scope中的greet方法--> 3 <button ng-click="greet({message: 'Hello', name: 'Tom'})">Click me!</button> 4 <div ng-transclude></div> 5 </div>
示例12(Directive偵聽事件,官方Demo):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('dragModule', []); 8 9 app.directive('myDraggable', ['$document', function ($document) { 10 return { 11 link: function (scope, element, attr) { 12 var startX = 0, startY = 0, x = 0, y = 0; 13 14 element.css({ 15 position: 'relative', 16 border: '1px solid red', 17 backgroundColor: 'lightgrey', 18 cursor: 'pointer' 19 }); 20 21 element.on('mousedown', function (event) { 22 // Prevent default dragging of selected content 23 event.preventDefault(); 24 startX = event.pageX - x; 25 startY = event.pageY - y; 26 $document.on('mousemove', mousemove); 27 $document.on('mouseup', mouseup); 28 }); 29 30 function mousemove(event) { 31 y = event.pageY - startY; 32 x = event.pageX - startX; 33 element.css({ 34 top: y + 'px', 35 left: x + 'px' 36 }); 37 } 38 39 function mouseup() { 40 $document.off('mousemove', mousemove); 41 $document.off('mouseup', mouseup); 42 } 43 } 44 }; 45 }]); 46 })(); 47 </script> 48 </head> 49 <body ng-app="dragModule"> 50 <span my-draggable>Drag ME</span> 51 </body> 52 </html>
示例13(Directive之間的相互做用,官方Demo):
1 <!DOCTYPE> 2 <html> 3 <head> 4 <script src="/Scripts/angular.js"></script> 5 <script type="text/javascript"> 6 (function () { 7 var app = angular.module('docsTabsExample', []); 8 9 app.directive('myTabs', function () { 10 return { 11 restrict: 'E', 12 transclude: true, 13 scope: {}, 14 controller: function ($scope) { 15 var panes = $scope.panes = []; 16 17 $scope.select = function (pane) { 18 angular.forEach(panes, function (pane) { 19 pane.selected = false; 20 }); 21 pane.selected = true; 22 }; 23 24 this.addPane = function (pane) { 25 if (panes.length === 0) { 26 $scope.select(pane); 27 } 28 panes.push(pane); 29 }; 30 }, 31 templateUrl: 'my-tabs.html' 32 }; 33 }); 34 35 app.directive('myPane', function () { 36 return { 37 // 指定必須有myTabs對象,若對象不存在則會報錯,見下面的圖1 38 require: '^myTabs', // ^ 表示將在父級的範圍內查找該對象, 沒有 ^ 表示在Directive內查找該對象, 若範圍指定錯誤沒法找到myTabs,js則會報錯 39 restrict: 'E', 40 transclude: true, 41 scope: { 42 title: '@' 43 }, 44 link: function (scope, element, attrs, tabsCtrl) { 45 tabsCtrl.addPane(scope); 46 }, 47 templateUrl: 'my-pane.html' 48 }; 49 }); 50 })(); 51 </script> 52 </head> 53 <body ng-app="docsTabsExample"> 54 <my-tabs> 55 <my-pane title="Hello"> 56 <h4>Hello</h4> 57 <p>Lorem ipsum dolor sit amet</p> 58 </my-pane> 59 <my-pane title="World"> 60 <h4>World</h4> 61 <em>Mauris elementum elementum enim at suscipit.</em> 62 <p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p> 63 </my-pane> 64 </my-tabs> 65 </body> 66 </html>
my-tabs.html:
1 <div class="tabbable"> 2 <ul class="nav nav-tabs"> 3 <li ng-repeat="pane in panes" ng-class="{active:pane.selected}"> 4 <a href="" ng-click="select(pane)">{{pane.title}}</a> 5 </li> 6 </ul> 7 <div class="tab-content" ng-transclude></div> 8 </div>
my-pane.html:
1 <div class="tab-pane" ng-show="selected" ng-transclude> 2 </div>