AngularJS自定義Directive


(編輯完這篇之后,發現本篇內容應該屬於AngularJS的進階,內容有點多,有幾個例子偷懶直接用了官方的Demo稍加了一些注釋,敬請見諒)。

前面一篇介紹了各種常用的AngularJS內建的Directives以及對應的代碼實例。這篇我們再看看如何創建自己的Directive吧!

什么時候需要自定義Directive?

1. 使你的Html更具語義化,不需要深入研究代碼和邏輯即可知道頁面的大致邏輯。

2. 抽象一個自定義組件,在其他地方進行重用。

 

看一下如下2個代碼片段:

示例1:

 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:

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標簽就知道這個頁面是在展示班級信息,班級信息中還有班主任的信息和所有學生的信息。

另外,示例1中,若一個班級的學生有30個,學生信息的Html會出現30次,如果將來發生變動,這30出學生信息的代碼都需要改動。

 

制作一個屬於自己的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:

1 <div>
2     <p>This is a custom template.</p>
3     <p>Your name: {{info.yourname}}</p>
4 </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種不同的代碼,能發現它們各自有不同的問題:

1. 示例4中,student-info的template中的所有表達式嚴重依賴Controller中的變量定義,導致student-info無法抽象成一個公共的學生信息展示模塊。

2. 示例5中,雖然使用ng-repeat封裝了代碼,但是還是存在依賴Controller中students變量的問題,示例5僅比示例4稍微好點。

3. 示例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>

 

參考資料

 AngularJS官方文檔:https://docs.angularjs.org/guide/directive

 CodeSchool快速入門視頻:http://campus.codeschool.com/courses/shaping-up-with-angular-js/intro


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM