在angular中,angular.module返回一个module对象,module对象拥有directive方法,directive方法可以用来创建自定义指令,自定义指令可以使html标签语义化。
先看一个实例:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='app'> <hello></hello> <script> var app = angular.module('app', []); app.directive('hello', function() { return { restrict: 'E', template: '<h1>Hello World!</h1>', replace: true }; }); </script> </body> </html>
restrict: 'E'
用来指定hello指令只能通过元素名方式调用。当然 restrict 还有其他的可选值:
字母 | 代表方式 | 示例 |
E | 元素名 | <hello></hello> |
A | 属性 | <div hello></div> |
C | 类名 | <div class=“hello”></div> |
M | 注释 | <!– directive:hello –> |
restrict 默认值为 EA, 即可以通过元素名和属性名来调用指令。
replace: true
用来在html中替换掉调用指令的标签。
模板扩展
默认情况下 directive 直接从父 scope 中共享属性,来看一个实例:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='app'> <div ng-controller="Controller"> <div my-customer></div> </div> <script> var app = angular.module('app', []); app.controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]).directive('myCustomer', function() { return { template: 'Name: {{customer.name}} Address: {{customer.address}}' }; }); </script> </body> </html>
如果你不太喜欢把template写到javascript中,可以使用templateUrl实现相同的效果,如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='app'> <div ng-controller="Controller"> <div my-customer></div> </div> <script> var app = angular.module('app', []); app.controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]).directive('myCustomer', function() { return { templateUrl: 'my-customer.html' }; }); </script> </body> </html>
Name: {{customer.name}} Address: {{customer.address}}
隔离指令的Scope
在上面的例子中,myCustomer指令已经可以很好的运行了,但是在给定的scope中myCustomer只能只能使用一次。为了达到多次使用指令接收不同的scope你很容易想到下面的方法:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='docsScopeProblemExample'> <div ng-controller="NaomiController"> <my-customer></my-customer> </div> <hr> <div ng-controller="IgorController"> <my-customer></my-customer> </div> <script> angular.module('docsScopeProblemExample', []) .controller('NaomiController', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .controller('IgorController', ['$scope', function($scope) { $scope.customer = { name: 'Igor', address: '123 Somewhere' }; }]) .directive('myCustomer', function() { return { restrict: 'E', template: 'Name: {{customer.name}} Address: {{customer.address}}' }; }); </script> </body> </html>
假如我们需要将指令内部的scope与指令外部的scope隔离,并且可以按需映射指令外部的scope到指令内部,此时我们可以借助指令的scope属性来实现,如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='docsIsolateScopeDirective'> <div ng-controller="Controller"> <my-customer info="naomi"></my-customer> <hr> <my-customer info="igor"></my-customer> </div> <script> angular.module('docsIsolateScopeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; $scope.igor = { name: 'Igor', address: '123 Somewhere' }; }]) .directive('myCustomer', function() { return { restrict: 'E', scope: { customerInfo: '=info' }, template: 'Name: {{customerInfo.name}} Address: {{customerInfo.address}}' }; }); </script> </body> </html>
directive 在使用隔离 scope 的时候,提供了三种方法同隔离之外的地方交互。这三种分别是
@
绑定一个局部 scope 属性到当前 dom 节点的属性值。结果总是一个字符串,因为 dom 属性是字符串。单向绑定,外部scope能够影响内部scope,但反过来不成立。&
提供一种方式执行一个表达式在父 scope 的上下文中。把内部scope的函数的返回值和外部scope的任何属性绑定起来。=
通过 directive 的 attr 属性的值在局部 scope 的属性和父 scope 属性名之间建立双向绑定。双向绑定,外部scope和内部scope的model能够相互改变。
在指令中操作DOM
如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='docsTimeDirective'> <div ng-controller="Controller"> Date format: <input ng-model="format"> <hr/> Current time is: <span my-current-time="format"></span> </div> <script> angular.module('docsTimeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.format = 'M/d/yy h:mm:ss a'; }]) .directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) { function link(scope, element, attrs) { var format, timeoutId; function updateTime() { element.text(dateFilter(new Date(), format)); } scope.$watch(attrs.myCurrentTime, function(value) { format = value; updateTime(); }); element.on('$destroy', function() { $interval.cancel(timeoutId); }); // start the UI update process; save the timeoutId for canceling timeoutId = $interval(function() { updateTime(); // update DOM }, 1000); } return { link: link }; }]); </script> </body> </html>
创建一个包装其他元素的指令
如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='docsTransclusionDirective'> <div ng-controller="Controller"> <my-dialog>Check out the contents, {{name}}!</my-dialog> </div> <script> angular.module('docsTransclusionDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Tobias'; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: {}, template: '<div class="alert" ng-transclude></div>' }; }); </script> </body> </html>
transclude会使指令内容访问指令外部的scope,如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='docsTransclusionExample'> <div ng-controller="Controller"> <my-dialog>Check out the contents, {{name}}!</my-dialog> </div> <script> angular.module('docsTransclusionExample', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Tobias'; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: {}, template: '<div class="alert" ng-transclude></div>', link: function(scope) { scope.name = 'Jeff'; } }; }); </script> </body> </html>
现在为dialog添加一个按钮,并在指令内部实现对dialog的行为操作,如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='docsIsoFnBindExample'> <div ng-controller="Controller"> {{message}} <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)"> Check out the contents, {{name}}! </my-dialog> </div> <script> angular.module('docsIsoFnBindExample', []) .controller('Controller', ['$scope', '$timeout', function($scope, $timeout) { $scope.name = 'Tobias'; $scope.message = ''; $scope.hideDialog = function(message) { $scope.message = message; $scope.dialogIsHidden = true; $timeout(function() { $scope.message = ''; $scope.dialogIsHidden = false; }, 2000); }; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: { 'close': '&onClose' }, template: '<div class="alert"><a href class="close" ng-click="close({message: \'closing for now\'})">×</a><div ng-transclude></div></div>' }; }); </script> </body> </html>
scope中的&attr用来在指令中暴漏一个API绑定行为
创建指令并为其添加事件监听
如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='dragModule'> <span my-draggable>Drag Me</span> <script> angular.module('dragModule', []) .directive('myDraggable', ['$document', function($document) { return { link: function(scope, element, attr) { var startX = 0, startY = 0, x = 0, y = 0; element.css({ position: 'relative', border: '1px solid red', backgroundColor: 'lightgrey', cursor: 'pointer' }); element.on('mousedown', function(event) { // Prevent default dragging of selected content event.preventDefault(); startX = event.pageX - x; startY = event.pageY - y; $document.on('mousemove', mousemove); $document.on('mouseup', mouseup); }); function mousemove(event) { y = event.pageY - startY; x = event.pageX - startX; element.css({ top: y + 'px', left: x + 'px' }); } function mouseup() { $document.off('mousemove', mousemove); $document.off('mouseup', mouseup); } } }; }]); </script> </body> </html>
指令通信
如下所示:
<html> <head> <meta charset="utf-8"> <title>自定义指令</title> <script src="angular.js"></script> </head> <body ng-app='docsTabsExample'> <my-tabs> <my-pane title="Hello"> <p>Lorem ipsum dolor sit amet</p> </my-pane> <my-pane title="World"> <em>Mauris elementum elementum enim at suscipit.</em> <p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p> </my-pane> </my-tabs> <script> angular.module('docsTabsExample', []) .directive('myTabs', function() { return { restrict: 'E', transclude: true, scope: {}, controller: ['$scope', function MyTabsController($scope) { var panes = $scope.panes = []; $scope.select = function(pane) { angular.forEach(panes, function(pane) { pane.selected = false; }); pane.selected = true; }; this.addPane = function(pane) { if (panes.length === 0) { $scope.select(pane); } panes.push(pane); }; }], templateUrl: 'my-tabs.html' }; }) .directive('myPane', function() { return { require: '^^myTabs', restrict: 'E', transclude: true, scope: { title: '@' }, link: function(scope, element, attrs, tabsCtrl) { tabsCtrl.addPane(scope); }, templateUrl: 'my-pane.html' }; }); </script> </body>
<div class="tabbable"> <ul class="nav nav-tabs"> <li ng-repeat="pane in panes" ng-class="{active:pane.selected}"> <a href="" ng-click="select(pane)">{{pane.title}}</a> </li> </ul> <div class="tab-content" ng-transclude></div> </div>
<div class="tab-pane" ng-show="selected"> <h4>{{title}}</h4> <div ng-transclude></div> </div>
myPane
指令拥有一个值为^^myTabs
的require
选项,当指令拥有此选项时,$complie将会抛出一个错误除非发现指定的控制器。^^
前缀告诉指令在当前元素父元素寻找控制器。
一个
^
前缀告诉指令在当前元素或当前元素父元素寻找控制器;没有前缀,告诉指令仅在当前元素寻找控制器。
在myPane
指令中,可以发现link
函数最后一个参数tabsCtrl
。当指令依赖一个控制器时,指令的link
函数会把控制器作为第四个参数传递过去。因此myPane
能够调用myTabs
的addPane
方法。
如果需要依赖多个控制器,指令中的require选项值以数组的形式来表示,传递给link函数相对应的参数也是一个数组,如下所示:
angular.module('docsTabsExample', []) .directive('myPane', function() { return { require: ['^^myTabs', 'ngModel'], restrict: 'E', transclude: true, scope: { title: '@' }, link: function(scope, element, attrs, controllers) { var tabsCtrl = controllers[0], modelCtrl = controllers[1]; tabsCtrl.addPane(scope); }, templateUrl: 'my-pane.html' }; });