(编辑完这篇之后,发现本篇内容应该属于AngularJS的进阶,内容有点多,有几个例子偷懒直接用了官方的Demo稍加了一些注释,敬请见谅)。
前面一篇介绍了各种常用的AngularJS内建的Directives以及对应的代码实例。这篇我们再看看如何创建自己的Directive吧!
什么时候需要自定义Directive?
使你的Html更具语义化,不需要深入研究代码和逻辑即可知道页面的大致逻辑。
抽象一个自定义组件,在其他地方进行重用。
看一下如下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:
<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>