AngularJS -- 指令(创建自定义指令)
点击查看AngularJS系列目录
转载请注明出处:http://www.cnblogs.com/leosx/
什么是指令
注:本指南是针对已经熟悉AngularJS基础知识的开发人员。如果你才刚刚开始,我建议查看系列教程。(这章是Angular的重点)
指令是一个Dom元素上的标签(和元素上的属性, CSS 类样式一样,属于这个Dom元素),它告诉AngualrJS的HTML 编译器($compile),去附加一个行为到这个Dom元素上去,这个行为可以改变这个Dom元素,或者这个Dom元素的子元素。
AngularJS 有一套自己内置的指令,如:ngBind,ngModel,ngClass等等...你也可以自定义自己的指令。当Angular应用程序起来之后,会先去加载Dom树,然后去匹配你的指令,然后执行。
HTML 编译器 -- 对于Angularjs来说,编译意味着递归去给HTML添加一些事件监听,让DOM元素和Angualr之间可以进行交互,相互作用。这里使用编译这个术语的原因是:添加事件监听这个过程,刚好反应了我们C#,Java等编程语言将源代码编译成应用的这个过程。
匹配指令
在我们编写我们的第一个自定义指令之前,我能需要知道AngularJS的HTML编译器是怎么确定在什么时候使用我们自定义的指令的。
下面这个例子中,我们可以看到<Input>元素匹配到了 ngModel 这个指令。
<input ng-model="foo">
下面这个例子也是匹配到了 ngModel 这个指令:
<input data-ng:model="foo">
AngularJS使用元素标签类型(eg:input)和属性名称来确定哪个元素匹配到了哪个指令。标准的指令(eg: ngModel)是区分大小写的,使用了驼峰命名法则。然而,由于HTML是不区分大小写的,所以后来也不区分大小写了…(感觉等于没说…) 但是通常我们都是使用 [-] 这个破折号来代表一个指令(通常,并不是必须)。
一般的使用方法如下:
1.使用 x- 或者 data- 在元素或者属性前。
2.使用 : 或者 - 或者 _ 来代替了驼峰命名法则。
<div 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 x-ng-bind="name"></span> <br/> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsBindExample', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)'; }]); })(); </script>
效果图:
$compile编译器会根据元素名称,属性,CSS类名,甚至是注释去匹配指令
下面展示了一个指令可以再哪些地方被引用:
<my-dir></my-dir> <span my-dir="exp"></span> <!-- directive: my-dir exp --> <span class="my-dir: exp;"></span>
建议1: 最好的使用指令方式是通过标签名,属性名,而不是使用注释和CSS样式类名。这样做可以让编译器更加容易的去正确匹配指令。
建议2: 注释指令通常用来解决Dom API的限制,进行跨越多个元素的操作。比如在 <Table> 元素内,去跨越多个元素进行操作。Angular1.2中引入了ng-repeat-start
和ng-repeat-end
指令,它们可以更好的去解决这个问题。鼓励开发者使用它们,而不是使用注释指令去解决这样的问题。
文本和属性绑定
在编译过程当中,编译器会根据文本的属性值和使用了$interpolate
服务的属性去查看这个HTML元素是否包含嵌入式的表达式。如果有,那么这些表达式会被注册到监视器中,并且会进入$digest阶段。下面是一个使用了$interpolate
表达式的示例:
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
ngAttr属性绑定
我们的浏览器有时候会挑剔什么是他们认为又要的属性值(也就是属性值不是你随便写的) 想想看下面的例子:
<svg> <circle cx="{{cx}}"></circle> </svg>
我们很希望Angular允许我们这样绑定,但是当我们打开浏览器控制台的时候,你就会看到如下错误:
Error: Invalid value for attribute cx="{{cx}}".
这是因为SVG DOM API的限制原因,你就不能写成: cx=”{{cs}}”了,而应该使用: ng-attr-cx 来解决这个问题。
如果一个属性的绑定的前缀是 ngAttr 而不是正规的 ng-attr-这种形式,那么在binding期间,它会被应用到相应的没有前缀的属性上去(eg: <circle ngAttr-cx=”a”></circle> 它是不会被应用到cx属性上去的)。因为当使用ngAttr前缀的时候,对于$interpolate阶段来说,任何标识都还不起作用。所以在这个阶段中所有的string结果都是 undefined的,既然是undefined的,那么对应的这个属性会被移除,而不再继续添加到元素上去。
我们可以如下去解决上面提到的问题:
<svg> <circle ng-attr-cx="{{cx}}"></circle> </svg>
注册指令
我们先来谈谈和注册指令相关的API,和controller一样,指令也是被注册到模块(modules)上的,创建指令的API是: module.directive(指令名称,功能函数)。module.directive(direName,func) ,这个func方法需要返回一个 配置参数对象,通常是一个Json对象,来告诉$compile编译器,进行这个指令匹配时的行为及动作.
这个func功能函数只会在当编译器第一次匹配到指令的时候执行一次。你可以在这里执行任何的初始化工作。这个func函数是被 $injector.invoke方法调用的。
友情提示:通常,为了避免与未来一些指令冲突,我们自定义的时候,都最好有一个自己的前缀。。。
模板扩展指令
我们想想,如果你现在需要一个模板,去展示客户的信息,而且这个模板会重复出现很多次,当你要改变一个地方,你就得去改很多次。这个是一个很好的情景让你去使用指令(directive)去简化你的模板(你该使用模板扩展指令了)。
下面,我们就是用一个例子来说明:
<div ng-app="docsSimpleDirective"> <div ng-controller="Controller"> <div my-customer></div> </div> <div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsSimpleDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { template: 'Name: {{customer.name}} Address: {{customer.address}}' }; }); })(); </script>
效果图:
当$compile编译器编译,并且连接了<div my-customer></div>之后,它将会继续尝试匹配这个元素的子元素。这就意味着你可以篡改其它指令哦!一会儿我们来看下面的例子。
还有需要注意的,上面的例子当中,我们返回了参数 template,当模板大小改变的时候,这是很烦人的哦!你的字符串会很长,很长! 哈哈!好像邪恶了。 除非你的模板就像上面例子一样,确实特别小,否则,我们还是建议你把模板吓到一个单独的HTML文件当中去,然后再指令的func中,我们返回的参数中,使用templateUrl选项,来指定我们的模板路径。
<div> <div ng-controller="Controller"> <div my-customer></div> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsTemplateUrlDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { templateUrl: 'my-customer.html' }; }); })(); </script>
my-customer.html的内容:
Name: {{customer.name}} Address: {{customer.address}}
效果图:
其实,templateUrl选项也可以是一个函数,返回要加载的HTML模板的URL路径,Angular会自动调用templateUrl函数,并且传入两个参数:ele(对应的元素), 和一个与这个元素相关的attr对象。
注意:在scope没有初始化之前,你是不能够在templateUrl函数中访问scope的,所以说,Controller的初始化一定是要在使用templateUrl函数之前。
<div ng-app="docsTemplateUrlDirective"> <div ng-controller="Controller"> <div my-customer type="name"></div> <div my-customer type="address"></div> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsTemplateUrlDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { templateUrl: function(elem, attr){ return 'customer-'+attr.type+'.html'; } }; }); })(); </script>
customer-name.html
Name: {{customer.name}}
customer-address.html
Address: {{customer.address}}
效果图:
再次注意:默认情况下,你创建的指令仅限于用于属性或者元素,为了创建一个可以用于类(class)的触发器,你需要使用到restrict选项。
restrict选项的几个经典选项:
‘A’ - 仅仅匹配属性名
‘E’ - 仅仅去匹配元素名
‘C’ - 仅仅匹配class名
友情提示:这些选项可以根据需要进行组合的哦!!
‘AEC’- 它可以匹配属性(attr)或者元素(ele)或者类名(class)。
我们来写一个例子,让我们的这个指令只能去匹配元素名,也就是说这个指令只能用于元素上。
<div ng-app="docsRestrictDirective"> <div ng-controller="Controller"> <my-customer></my-customer> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsRestrictDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }]) .directive('myCustomer', function() { return { restrict: 'E', // 注意这里哦! templateUrl: 'my-customer.html' }; }); })(); </script>
my-customer.html内容如下:
Name: {{customer.name}} Address: {{customer.address}}
效果图:
更多的关于restrict选项,你可以参考API文档(你知道的,有可能访问不了哦!肿么破,这里不说哈!)
更大的问题来了。什么时候我该用属性,什么时候我们该用元素呢? 不出意外,其实你应该能知道,什么时候该用什么。 当我们要创建一个控件的时候,那我们就得使用元素匹配方式。当你需要给你现在的元素增加一些新功能,进行修饰,扩展的时候,我们就得使用属性了。不多解释…
指令独立的scope
大家可以想一想,上面说的关于指令的使用的例子,就会发现它有个致命的缺陷,一个Scope(也就是一个Controller)里面你只能使用一次我们自定义的myCustomer指令。这样的话,我们如果想要重复的使用这个指令的时候,你就得声明与之对应个数的Controller,Oh~~ my god !!! 你肯定瞬间不想使用Angular了,不错不要着急,会有解决方法的。
我们可以使用给每个directive(指令)一个独立的scope来进行分离,然后将外部Controller中的scope映射到directive(指令)内部的独立的scope上去。这样,我们就可以调用自己独立的scope啦! 是不是很好哦!要实现上面的独立,AngularJS给我们提供了一个directive的scope参数,看下面示例,你就明白了!
<div ng-app="docsIsolateScopeDirective"> <div ng-controller="Controller"> <my-customer info="naomi"></my-customer> <hr> <my-customer info="igor"></my-customer> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ 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' }, templateUrl: 'my-customer-iso.html' }; }); })(); </script>
my-customer-iso.html内容如下:
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
效果图:
我们来解释一下上面,在<my-customer>元素中,我们给它的info属性中,绑定了Controller中的Scope中的naomi属性上去了。 第二个<my-customer>元素的info属性我们是给绑定了igor属性。
我们来仔细看看scope选项:
// ...
scope:{
customerInfo: '=info'
},
// ...
scope选项中,包涵了所有关于这个指令独立的scope信息,在上面例子中,它只包涵了一个属性:
customerInfo 是这个指令独立的scope的属性名,它的值为 =info ,这是在告诉$compile编译器,去把这个属性绑定到一个叫做info的属性上去。。。
注意:上面的绑定是一个很标准的绑定方式,但是如果你想绑定成这样:<div bind-to-this=”thing”>,你需要将customerInfo的值设置为 =bindToThis (也就是驼峰命名法则)
我们来考虑还有一种情况,如果说你想就让绑定的属性名和你这个指令独立的scope的属性名一样的话,你可以使用下面这个简写语法:
...
scope: {
// same as customer: '=customer'
customer: '='
},
...
除了可以将不同的数据绑定到directive 独立的scope上之外,使用独立的scope还有另外一个效果.
不解释,先来段代码:
<div ng-app='docsIsolationExample'> <div ng-controller="Controller"> <my-customer info="naomi"></my-customer> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsIsolationExample', []) .controller('Controller', ['$scope', function($scope) { $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; $scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' }; }]) .directive('myCustomer', function() { return { restrict: 'E', scope: { customerInfo: '=info' }, templateUrl: 'my-customer-plus-vojta.html' }; }); })(); </script>
my-customer-plus-vojta.html 内容如下:
Name: {{customerInfo.name}} Address: {{customerInfo.address}} <hr> Name: {{vojta.name}} Address: {{vojta.address}}
效果图如下:
请注意: {{vojta.name}} 和 {{vojta.address}} 值是空的。也就是他们根本没有被定义。虽然我们在controller中的scope中定义了vojta属性,但是,它在我们自定义的directive(指令)中,是不可用的。。。
顾名思义,在这个directive的独立scope中,除了实体模型你需要明确的增加之外,你可以添加任何的东西到这个独立的scope中,那么这样的话,我们就可以在这里进行重构组件,这样的话,我们就可以防止控件改变了你的模型的状态,当然,除了你明确的指定将这个模型传入进来了,你都传进来了,就可以改变了。
注意:在一般情况下,一个scope是从上级的scope派生来的,但是这里是一个特例哦! directive(指令)中的scope不是那样的,如果你想要了解更多的关于定义指令的对象- scope 你可以看看这里。
建议: 当我们想要让我们自定义的组件在你的整个Angular应用程序中重用的话,你就要使用独立的scope,但是,如果你只使用一次的话,就可以不用了。不过个人建议,最好还是使用吧! *^_^*
创建一个指令,去操作DOM
在这个例子当中。我们去创建一个指令,用于显示当前的时间。一秒更新一次,我们就需要更新DOM去显示当前时间。
通常,我们如果想要在指令中去修改DOM元素的话(Angular不建议你直接去操作DOM),会使用 link 选项 ,link 选项的签名如下:
function link(scope, element, attrs){ ...... }
[ scope ]是一个Angular的scope对象
[ element ]是这个指令所匹配到的那个元素。
[ attrs ] 是一个规范的key-value键值对儿的hash对象
在link 方法中,我们想每秒钟更新一下显示时间, 可以使用Angular自带的 $interval 服务,相比之下,它要比 $timeout 服务更加好用,而且更加的易于端对端测试,它会保证在你的测试完成之前,你的$timeout 里面的所有执行都已完成,而且,它也可以保证当你把directive(指令)移除了之后,会自动的把 $interval 服务给卸载掉,从而不会造成内存的泄露。
是骡子是马,咱们还是直接拉出来溜溜!! 直接上代码!
<div ng-ap="docsTimeDirective"> <div ng-controller="Controller"> Date format: <input ng-model="format"> <hr/> Current time is: <span my-current-time="format"></span> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ 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>
看看效果图(自己去实践哈,这里不做jif的动态图了哈):
这里有几个需要注意的地方。 就像module.contoller API一样,module.directive函数也是依赖注入的,因此,我们可以注入 $interval 和 dateFilter(过滤器),然后再link 函数中进行使用了。
我们注册了事件 element.on(‘$destory’, … ) 在 $destory 事件中,发生了什么事情呢?
在AngularJS里有几个特别的事件。当一个DOM节点在Angular的编译器中要被销毁,会触发 $destory 事件。同样的,当一个AngularJS的 scope 对象被销毁的时候,也会广播一个 $destory 事件,给那些监听scope的对象。
通过监听这个事件,你可以去删除哪些可能会导致内存溢出的监听器。已注册监听的scope和元素(element)会在它们被销毁的时候自动清理掉,我们是不用去手动销毁的。但是,如果你你注册了一个服务,或者一个监听DOM节点的监听器,而且它们还被删除了,那么,你就得自己手动去清理。否则可能会造成内存泄露。
建议:指令应该由他们自己去清理的。也就是说,你需要调用 element.on( ‘$destory’, … ) 或者 scope.$on( ‘$destory’, …) 去运行一个清理函数(func),来保证当这个指令被清除的时候,会清除掉和它相关的资源。
我们来创建一个封装了其它元素的指令:
现在你知道了怎么传递一个model到 directive上的独立scope上去。 但是,有的时候,我们需要传递整个模板,而不只是一个字符串或者一个对象而已,那怎么办呢? 假设我们现在需要创建一个对话框组件。对话框应该可以显示任意对象,该怎么实现呢? 这需要用到另外的一个选项:transclude 直接上代码:
<div ng-app="docsTransclusionDirective"> <div ng-controller="Controller"> <my-dialog>Check out the contents, {{name}}!</my-dialog> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsTransclusionDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Tobias'; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, templateUrl: 'my-dialog.html' }; }); })(); </script>
my-dialog.html页面内容:
<div class="alert" ng-transclude> </div>
我们来看看效果图:
那这个 transclude 选项做了些什么事情呢? transclude 选项会让指令去访问来自指令之外的 scope 。而不是去访问自己内部的scope 。
还是使用例子来解释下吧! 下列中,请注意,我们添加了一个 link 函数,它重新设置了scope中的name属性的值为 “Jeff”,你猜猜在 {{name}} 绑定中,会显示什么呢?
<div ng-app="docsTransclusionExample"> <div ng-controller="Controller"> <my-dialog>Check out the contents, {{name}}!</my-dialog> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsTransclusionExample', []) .controller('Controller', ['$scope', function($scope) { $scope.name = 'Tobias'; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: {}, templateUrl: 'my-dialog.html', link: function (scope, element) { scope.name = 'Jeff'; } }; }); })(); </script>
my-dialog.html 中的内容如下:
<div class="alert" ng-transclude> </div>
一般来说,我们期望 {{name}} 里面显示的是 “Jeff”,但是,but,事实上,它显示的依然是 “Tobias”。选项 transclude 改变了 scope的嵌套方式,它会让{{name}} 指令去访问指令外的scope,而不是去访问指令内部的scope 。
注意:如果你的指令没有创建自己的scope,那么你再在link 中, scope.name= ‘Jeff’的话,就会看到输出的结果是 “Jeff”了,因为这样相当于重新定义了外部scope的name属性值为 “Jeff”。不要绕晕了哦!
这个对于那些需要包涵一些不同内容(内容可变)的指令来说非常有意义哦! 没有这个特性的话,你就得分别创建多个不同的实例了。
建议: 从我们的使用来看就可以知道, transclude选项的使用情景应该是当你的指令内容中,需要包含经常变化或者是不定的地方。
接下来,我们来创建一个有按钮的对话框,它允许我们去绑定一些我们自定义的行为上去,直接上代码:
<div ng-app="docsIsoFnBindExample"> <div ng-controller="Controller"> <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()"> Check out the contents, {{name}}! </my-dialog> </div> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsIsoFnBindExample', []) .controller('Controller', ['$scope', '$timeout', function($scope, $timeout) { $scope.name = 'Tobias'; $scope.hideDialog = function () { $scope.dialogIsHidden = true; $timeout(function () { $scope.dialogIsHidden = false; }, 2000); }; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: { 'close': '&onClose' }, templateUrl: 'my-dialog-close.html' }; }); })(); </script>
my-dialog-close.html 内容如下:
<div class="alert"> <a href class="close" ng-click="close()">×</a> <div ng-transclude></div> </div>
再来看看效果图:
当我们点击关闭按钮的时候,它会把下面那句话隐藏掉。两秒钟后,再显示出来,自己亲自去试!
正如我们所知道一样,我们可以在指令的scope中定义一些函数(func),然后在scope的上下文中去调用它,但是注意的是,这个函数并需先定义。
我们之前看到了怎么样在scope选项中使用 =attr 。但是上面的例子当中,我们使用了 &attr 实例。这里的这个 & 绑定(别看只是一个符号,它是一个绑定哦!) 允许指令在原始scope的特定时间,去触发调用一个表达式。这个表达式不限制,任意表达式都行,包括那些包涵了方法,函数调用的表达式。正因如此, & 绑定符,是一个很理想的 绑定 指令的回调函数 的方式。
当用户点击对话框中的X按钮的时候,指令的close 函数将被调用,由于ng-click的执行,事实上,它会调用它指令自己独立的scope,而那个scope选项(是选项哦,不要搞晕了),又去调用了上下文中父级scope的 hideDialog() 方法。因此实现了控制器中的hideDialog功能。
建议: 当你想要给你的自定义指令暴露一个绑定行为的API的时候,你就可以使用scope选项 的 &attr 。
创建一个增加事件监听的指令
以前,我们使用 link 选项函数去创建一个指令去操作DOM元素。下面,我们来创建一个能够反映使用它的元素的事件的指令。这个示例中,我们会创建一个可以让元素拖动的指令。
先上例子:
<div ng-app="dragModule"> <span my-draggable>Drag ME</span> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('dragModule', []) .directive('myDraggable', ['$document', function($document) { return 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>
效果图:
创建一个用于通信的指令。
还是先上例子吧!
<div ng-app="docsTabsExample"> <my-tabs> <my-pane title="Hello"> <h4>Hello</h4> <p>Lorem ipsum dolor sit amet</p> </my-pane> <my-pane title="World"> <h4>World</h4> <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> </div> <script src="https://code.angularjs.org/1.3.0/angular.min.js"></script> <script type="text/javascript"> (function(){ angular.module('docsTabsExample', []) .directive('myTabs', function() { return { restrict: 'E', transclude: true, scope: {}, controller: function($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>
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" ng-transclude> </div>
效果图:
在 myPane 指令中,使用到了一个 require选择,其值为: ^myTabs 。当一个指令使用了这个选项的, $compile 编译器会需要让你提供一个 contorller ,不然的话,它会抛出一个异常的。 使用 ^ 前缀意味着这个指令会在它的父级元素(如果没有 ^ 前缀的话,它会在自己的元素里面查找Controller)里面去查找Controller。
那么,这个 myTabs controller又是从哪里来的呢? 指令可以使用Controller选项去指定controller从哪里来! 就像上面例子一下, myTabs 指令使用了这个选项。 这个选项的值和AngularJS的控制器( ngController )写法一样,这个选项也可以附加一些模板给这个指令。
如果有必要引用一个controller或者任何的函数,然后将他们绑定到模板的controller的scope上去的话,你可以使用 controllerAs 选项来为controller指定一个别名。 你需要给指令定义一个用于使用的scope配置,在指令作为一个组件来使用的时候,这是特别有用的哦!
再来回顾一下myPane 的定义,注意 link函数的最后的一个参数: tabsCtrl。 当一个指令需要一个控制器的时候,在 link 函数中,会传到第四个参数中去。我们可以在第四次参数中得到。 利用这一点,myPane可以调用myTabs中的addPane功能了。
如果你需要多个控制器的话,可以给 require选项传入一个数组。如下:
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'
};
});
有些读者可能很想知道 link 函数和 controller之间的区别。 它们最基本的区别是,controller是一个暴露的API, 而link 函数是让元素和控制器进行交互的。
建议: 当你确定你需要向外,向其他指令暴露一个API的时候,你就得使用controller,其它的情况,还是使用 link比较好!!!
简单说明一下,这一章很重要,但是这里呢,我们只是写了一些指令的用例,但是每个用例都是你去创造更多指令的一个很不错的起点哦!! 发挥你的想要把!
如果你有兴趣了解一下编译过程的话,你可以点击这里。
如果你想要查看一下 $compile API 编译器提供的详细的指令的话,你可以点击 这里。