angular学习(十二)—— Directive
转载请写明来源地址:http://www.52php.cn/article/p-tasriubw-bph.html directive介绍directive是DOM元素上的标记,告诉angularjs的HTML编译器( 看到编译两个字,很多人会感到很懵,javascript不是解释执行的吗。其实这里的编译是因为,给html附加directive的递归过程很像是编译源代码的过程,所以才叫编译。 angularjs内置了一套directive,像ngBind,ngModel和ngClass。就像你创建controller和service一样,你也可以创建自己的directive。当angularjs启动你的app时,它会遍历DOM来匹配directive。 匹配directive在写directive之前,先了解一下angularjs的html编译器怎么觉得何时需要使用给定的指令。 就像element匹配selector一样,当directive是element的声明的一部分时,我们就把这个叫做element匹配directive。
规范化angular规范了元素的标签名和属性名,以此来决定元素和指令的匹配。我们一般提到指令时都是用他们区分大小写的骆驼命名法,比如ngModel。但是HTML是不区分大小写的,因此我们在DOM中要把他们写成全部小写的作为元素的属性,并且中间用破折号分割,例如ng-model。 标准如下:
下面的例子中,所有写法都是一样匹配 <!DOCTYPE html>
<html xmlns:ng="http://www.w3.org/1999/xhtml" xmlns:data-ng="http://www.w3.org/1999/xhtml">
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<script> angular.module('docsBindExample',[]) .controller('Controller',['$scope',function($scope) { $scope.name = 'Max Karl Ernst Ludwig Planck (April 23,1858 - October 4,1947)'; }]); </script>
<body ng-app="docsBindExample">
<div ng-controller="Controller">
Hello <input ng-model='name'> <hr/>
<span ng-bind="name"></span> <br/>
<span ng:bind="name"></span> <br/>
<span ng_bind="name"></span> <br/>
<span data-ng-bind="name"></span> <br/>
<span data-ng:bind="name"></span> <br/>
<span x-ng-bind="name"></span> <br/>
<span x-ng_bind="name"></span> <br/>
</div>
</body>
</html>
最佳的方式是 directive类型
angular内置的指令都可以匹配元素name,属性,class的name和注释中的指令,下面演示了匹配指令的几种方法。 <my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
在元素name和属性使用directive,要比在注释和class的name上好很多,这样更容易判断元素匹配哪些指令。 注释指令通常用在跨越多个元素不方便指令的情况下,比如在 创建directive和controller和service一样,directive也是由module注册的。可以使用 当编译器第一次匹配到指令时,指令的工厂函数只调用一次。因此,你可以在函数里做一些初始化的操作。函数是由 我们将通过几个directive的例子来深入了解一下不同选项和编译过程。 需要注意的一点是,为了不和一些未来的标准冲突,directive名字最好有自己的前缀。例如你创建了一个 下面的例子中我们就用my作为directive的前缀。 模版扩展directive假如你的模版有一大块都是展示客户的信息,这个模版在你代码中重复了好多次,当你需要修改模版的一个地方,其他的几个也需要修改,这就是一个使用directive简化模版的好时机。 下面是一个简单的例子,用静态模版代替html中的内容。 <!DOCTYPE html> <html> <head> <meta charset="uft-8"/> <title></title> </head> <script src="script/angular.min.js"></script> <body ng-app="docsSimpleDirective"> <div ng-controller="Controller"> <div my-customer></div> </div> </body> <script> angular.module('docsSimpleDirective',function ($scope) { $scope.customer = { name: 'Naomi',address: '1600 Amphitheatre' }; }]) .directive('myCustomer',function () { return { template: 'Name: {{customer.name}} Address: {{customer.address}}' }; }); </script> </html>
注意在这个directive的例子中我们使用了数据绑定,当 在上面的例子中,我们列出了template选项的值,但是如果template内容越来越多的话,这会很头疼。 除非你的template非常小,否则的话需要把内容放置在一个html文件中,然后用templateUrl选项加载它。templateUrl的工作原理很像ngInclude,我们把上面的例子用templateUrl改一下: docsSimpleDirective.html: <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="docsSimpleDirective">
<div ng-controller="Controller">
<div my-customer></div>
</div>
</body>
<script> angular.module('docsSimpleDirective',function () { return { templateUrl: 'my-customer.html' }; }); </script>
</html>
my-customer.html: Name: {{customer.name}} Address: {{customer.address}}
templateUrl也可以是个函数,这个函数返回一个可以被directive加载的template的url。AngularJS会传入两个参数调用这个函数,directive所添加在的元素本身elem,还有这个元素的属性attr。 要注意到,你不能再templateUrl的函数中访问 docsTemplateUrlFunctionDirective.html: <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="docsTemplateUrlFunctionDirective">
<div ng-controller="Controller">
<div my-customer type="name"></div>
<div my-customer type="address"></div>
</div>
</body>
<script> angular.module('docsTemplateUrlFunctionDirective',function($scope) { $scope.customer = { name: 'Naomi',function() { return { templateUrl: function(elem,attr) { return 'customer-' + attr.type + '.html'; } }; }); </script>
</html>
customer-address.html: Address: {{customer.address}}
customer-name.html: Name: {{customer.name}}
当你创建指令时,它仅仅默认的限定了元素名称和属性名称,如果想要class名称也触发,那就要使用 restrict选项列表:
这些限定也可以组合起来:
上面的例子用 docsRestrictDirective.html: <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="docsRestrictDirective">
<div ng-controller="Controller">
<my-customer></my-customer>
</div>
</body>
<script> angular.module('docsRestrictDirective',function() { return { restrict: 'E',templateUrl: 'my-customer.html' }; }); </script>
</html>
那么什么时候用元素什么时候用属性呢? directive的范围隔离
重用directive笨一点的办法就是写多一个controller: docsScopeProblemExample.html <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="docsScopeProblemExample">
<div ng-controller="NaomiController">
<my-customer></my-customer>
</div>
<hr>
<div ng-controller="IgorController">
<my-customer></my-customer>
</div>
</body>
<script> angular.module('docsScopeProblemExample',[]) .controller('NaomiController',address: '1600 Amphitheatre' }; }]) .controller('IgorController',function ($scope) { $scope.customer = { name: 'Igor',address: '123 Somewhere' }; }]) .directive('myCustomer',function () { return { restrict: 'E',templateUrl: 'my-customer.html' }; }); </script>
</html>
当然这可不是个好办法。我们需要做的就是把directive的范围内部和外部的scope区分开,然后把外部scope映射到内部范围。通过创建隔离范围(isolate scope)来做到这一点,这需要使用directive的scope选项。 docsIsolateScopeDirective.html: <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="docsIsolateScopeDirective">
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
<hr>
<my-customer info="igor"></my-customer>
</div>
</body>
<script> angular.module('docsIsolateScopeDirective',function($scope) { $scope.naomi = { name: 'Naomi',address: '1600 Amphitheatre' }; $scope.igor = { name: 'Igor',scope: { customerInfo: '=info' },templateUrl: 'my-customer-iso.html' }; }); </script>
</html>
my-customer-iso.html: Name: {{customerInfo.name}} Address: {{customerInfo.address}}
我们再仔细看下scope选项: //...
scope: {
customerInfo: '=info'
},//...
scope选项是一个对象,该对象包含了每一个隔离范围绑定的属性,在这个例子中就一个属性:
directives的scope选项的命名标准和directive名称一样。为了绑定到 如果属性名称和你要绑定到directive的内部范围的值相同,你可以缩写为: ...
scope: {
// same as '=customer'
customer: '='
},...
除了绑定不同的数据到同一个directive的内部范围外,directive隔离范围还有另一种用法。 我们再增加一个属性 docsIsolationExample.html: <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="docsIsolationExample">
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
</div>
</body>
<script> angular.module('docsIsolationExample',address: '1600 Amphitheatre' }; $scope.vojta = { name: 'Vojta',address: '3456 Somewhere Else' }; }]) .directive('myCustomer',scope: { customerInfo:'=info',},templateUrl: 'my-customer-plus-vojta.html' }; }); </script>
</html>
my-customer-plus-vojta.html: Name: {{customerInfo.name}} Address: {{customerInfo.address}} <hr> Name: {{vojta.name}} Address: {{vojta.address}}
然后发现 顾名思义,directive的隔离范围隔离了一切,除了你明确添加到directive的scope选项中的model外。这在构建可重用的组件时相当有用,因为可以在更改model时保护组件,只有明确在scope中指定的model才会影响到组件。 记住一点,scope会继承自父scope,而隔离scope则不会。 创建一个操作DOM的directive下面的例子演示的是一个显示当前时间的指令的创建,显示的时间一秒更新一次。 操作DOM的指令通常使用 link对应的是一个函数
在我们的link函数中,我们每秒更新一次显示的时间,另外用户在输入框更改了指令绑定了的格式化字符串时也要更新显示时间。这比 <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<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>
</body>
<script> angular.module('docsTimeDirective',function ($scope) { $scope.format = 'M/d/yy h:mm:ss a'; }]) .directive('myCurrentTime',['$interval','dateFilter',function ($interval,dateFilter) { function link(scope,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>
</html>
和 我们注册了一个事件element.on(‘$destroy’,…),那么是如何激活 通过监听这个事件,你可以移除可能会引起内存泄漏的事件监听器。注册下scope和element上的监听器会随着scope和element的销毁而跟着销毁,但是注册在service或者注册在DOM节点上的监听器则不会,它们需要显示的移除,否则就要承担内存泄漏的风险。 如果想显示清除可能导致内存泄漏的监听器,那么就需要使用 创建封装其他element的directive在之前的例子中,我们已经看到,我们可以通过隔离范围(isolate scope)把model传到directive中,但是有时候我们想要传递整个template,而不仅仅是一个字符串或者一个对象。假设我们要创建一个 那么,我们就需要 docsTransclusionDirective.html: <!DOCTYPE html> <html> <head> <meta charset="uft-8"/> <title></title> </head> <script src="script/angular.min.js"></script> <body ng-app="docsTransclusionDirective"> <div ng-controller="Controller"> <my-dialog>Check out the contents,{{name}}!</my-dialog> </div> </body> <script> angular.module('docsTransclusionDirective',function($scope) { $scope.name = 'Tobias'; }]) .directive('myDialog',transclude: true,scope: {},templateUrl: 'my-dialog.html' }; }); </script> </html>
my-dialog.html: <div class="alert" ng-transclude></div>
transclude选项使得directive的上下文可以访问指令外部的范围而不是访问指令内部的范围。 为了说明这一点我们再看一个例子,我们在上面例子的基础上增加了一个link函数,该函数将name重命名为Jeff,你认为{{name}}绑定会最终显示什么呢。 docsTransclusionExample.html: <!DOCTYPE html> <html> <head> <meta charset="uft-8"/> <title></title> </head> <script src="script/angular.min.js"></script> <body ng-app="docsTransclusionExample"> <div ng-controller="Controller"> <my-dialog>Check out the contents,{{name}}!</my-dialog> </div> </body> <script> angular.module('docsTransclusionExample',templateUrl: 'my-dialog.html',link: function(scope) { scope.name = 'Jeff'; } }; }); </script> </html>
我们通常都会认为{{name}}会变成Jeff,但实际上仍然会是Tobias。 transclude选项改变了scope嵌套的方式,它使得指令访问指令外部的scope,而不是指令内部的scope,它给出了访问外部scope的上下文。 请注意,如果directive没有创建自己的scope,那么 封装一些内容的directive还是非常有意义的,如果不这样做,你需要每个model单独传到directive中,如果你要一个个传递model,那么你就不能传递任意的内容。当你想要传递内容时就使用 下面我们加个按钮在 docsIsoFnBindExample.html: <!DOCTYPE html> <html> <head> <meta charset="uft-8"/> <title></title> </head> <script src="script/angular.min.js"></script> <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> </body> <script> angular.module('docsIsoFnBindExample','$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',scope: { 'close': '&onClose' },templateUrl: 'my-dialog-close.html' }; }); </script> </html>
my-dialog-close.html: <div class="alert">
<a href class="close" ng-click="close({message: 'closing for now'})">×</a>
<div ng-transclude></div>
</div>
我们想要在directive的scope调用函数,但是函数运行在它注册的scope上下文中。 我们在之前等例子中已经了解过在directive的scope选项中使用 当用户点击对话框中的x时,directive的 通过 你可以在需要directive给绑定暴露一些api时在directive的scope选项中使用 创建增加事件监听器的directive之前我们已经用link函数创建了可以操作DOM的directive,在其之上,我们来创建可以监听其元素事件的directive。 以下是可以拖动元素的directive: <!DOCTYPE html>
<html>
<head>
<meta charset="uft-8"/>
<title></title>
</head>
<script src="script/angular.min.js"></script>
<body ng-app="dragModule">
<span my-draggable>Drag Me</span>
</body>
<script> angular.module('dragModule',[]) .directive('myDraggable',['$document',function ($document) { return { link: function (scope,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>
</html>
创建相互通信的directive你可以在模版中将directives组合着用,有时候需要把几个directives组合成一个组件。 比如下面的点击切换tab的例子: docsTabsExample.html: <!DOCTYPE html> <html> <head> <meta charset="uft-8"/> <title></title> </head> <script src="script/angular.min.js"></script> <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> </body> <script> angular.module('docsTabsExample',[]) .directive('myTabs',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',scope: { title: '@' },link: function (scope,tabsCtrl) { tabsCtrl.addPane(scope); },templateUrl: 'my-pane.html' }; }); </script> </html>
my-tabs.html: <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>
my-pane.html: <div class="tab-pane" ng-show="selected"> <h4>{{title}}</h4> <div ng-transclude></div> </div>
myPane指令有个值等于^^myTabs的require选项。当directive指令使用require选项,如果找不到对应的controller, 那么myTabs控制器从何而来呢,不出所料,有一个directive会通过controller选项指定controller,如你所见,myTabs指令使用了这个选项。像ngController一样,该选项为directive的模版附加一个controller。 如果有必要从模版中引用controller或者controller上的函数,你可以使用controllerAs选项来指定controller的别名,为了使用这个配置,directive需要定义scope选项。当directive被用作一个组件时是这样做非常有用的。 再看一下myPane的定义,注意到link函数的第四个参数是tabsCtrl。当directive通过require指定了controller之后,它就可以通过link函数的第四个参数来接收指定的controller,这样myPane就可以调用myTabs的addPane函数。 如果需要多个controller,directive的require选项也可以接收数组参数。那么相应的,link函数的第四个参数也会是一个数组。 angular.module('docsTabsExample',[])
.directive('myPane',function() {
return {
require: ['^^myTabs','ngModel'],scope: {
title: '@'
},link: function(scope,controllers) {
var tabsCtrl = controllers[0],modelCtrl = controllers[1];
tabsCtrl.addPane(scope);
},templateUrl: 'my-pane.html'
};
});
link函数和controller之间的区别在于:controller可以暴露api出来,link函数通过require选项去使用controller。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |