• 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

  • 相关阅读:
    mysql主从只同步部分库或表
    springboot~aspect通过@annotation进行拦截
    ELK~fluentd将日志文件增量读到es里
    怎么自学 Java ?和盘托出
    一个后端开发的 Vue 笔记【入门级】
    企业级数据大屏设计如何实现
    Vue.js官方中文教程
    vue学习笔记 ---- 系列文章
    基于Asp.net core Kestrel的超迷你http服务器 ---- AServer
    Docker学习—概念及基本应用
  • 原文地址:https://www.cnblogs.com/wushangjue/p/4508764.html
Copyright © 2020-2023  润新知