本文实例讲述了AngularJS创建自定义指令的方法。分享给大家供大家参考,具体如下:
这是一篇译文,来自angular开发者说明的指令。主要面向已经熟悉angular开发基础的开发者。这篇文档解释了什么情况下需要创建自己的指令,和如何去创建指令。
什么是指令
从一个高的层面来讲,指令是angular $compile服务的说明,当特定的标签(属性,元素名,或者注释) 出现在DOM中的时候,它让编译器附加指定的行为到DOM上。
这个过程是很简单的。angular内部有很用这样自带的指令,比如说ngBind, ngView,就像你创建控制器和服务一样,你也可以创建自己的指令。当angular启动的时候,Angular的编译器分析html去匹配指令,这允许指令注册行为或者改变DOM。
匹配指令
在写指令之前,我们首先需要知道的是angular是如何匹配到一个指令的,在以下的例子我们说input元素匹配到ngModel指令.
<input ng-model="foo"></div>
下面这种方法同样也会匹配到ngModel:
<input data-ng:model="foo"></div>
Angular会规范化一个元素的标签名和属性名,以便确定哪一个元素匹配到哪一个指令。我们在js中是通过使用规范化后的驼峰式的名字来引用指令(比如ngModel)。在HTML中常常使用'-'划定的属性名字来调用指令(比如说ng-model).
规范化的处理过程:
-去掉元素或属性上面的x-和data-的前缀
-转化':','-'和‘_-'形式的命名为驼峰式拼写
以下例子展示了用不同的方式匹配到ngBind指令
<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>
Best Practice: 优先使用'-'格式的命名(比如说ng-bind匹配ngBind)。如果你想在HTML验证工具中通过,你可以用'data-'前缀的方式(比如data-ng-bind)。其他格式的命名是因为历史遗留的原因存在,避免使用它们。
$compile服务可以基于元素的名字,属性名,类名,和注释来匹配指令
所有Angular内部提供的指令都匹配属性名,标签名,注释,或者类名。以下不同的方式都可以被解析到
<my-dir></my-dir> <span my-dir="exp"></span> <!-- directive: my-dir exp --> <span class="my-dir: exp;"></span></div>
Best Practice: 优先利用标签名和属性名的方式使用指令。这样子更容易理解指定的元素匹配到了哪个元素。
Best Practice: 注释的方式通常被用在DOM API限制创建跨越多个元素的指令,比如说table元素,限制重复嵌套,这样就要用注释的方式。在AngularJS 1.2版本中,通过使用ng-repeat-start 和 ng-repeat-end 作为一个更好的方案来解决这个问题。在可能的情况下,推荐使用这种方式。
文本和属性的绑定
在编译过程中,编译器会使用$interpolate服务来检测匹配到的文本和属性值是否包含内嵌表达式。这些表达式被注册为watches,可以在digest循环时被更新。
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a></div>
ngAttr属性的绑定
浏览器有些时候会对它认为合法的属性值非常的挑剔(就是某些元素的属性是不可以任意赋值的,否则会报错)。
比如:
<svg> <circle cx="{{cx}}"></circle> </svg></div>
使用这样的写法时,我们会发现控制台中报错Error: Invalid value for attribute cx="{{cx}}". .这是由于SVG DOM API的限制,你不能简单的写为cx="{{cx}}".
使用ng-attr-cx 可以解决这个问题
如果一个绑定的属性使用ngAttr前缀(或者ng-attr), 那么在绑定的时候将会被应用到相应的未前缀化的属性,这种方式允许你绑定到需要马上被浏览器处理的属性上面(比如SVG元素的circle[cx]属性)。
所以,我们可以这样写来修复以上的问题:
<svg> <circle ng-attr-cx="{{cx}}"></circle> </svg></div>
创建指令
首先我们来谈论下注册指令的API,跟controller一样,指令是注册在module上,不同的是,指令是通过module.directive API来注册的。module.directive接受的是一个规范化的名字和工厂函数,这个工厂函数返回一个包含不同配置的对象,这个对象用来告诉$compile服务如何进行下一步处理。
工厂函数仅在编译器第一次匹配到指令的时候调用一次。通常在工厂函数中执行初始化的工作。该函数使用$injector.invoke调用,所以它可以像controller一样进行依赖注入。
Best Practice: 优先返回一个定义好的对象,而不是返回一个函数。
接下来,我们先会了解一些常见的例子,然后再深入了解不同的配置项的原理和编译过程。
Best Practice: 为了避免与某些未来的标准命名冲突,最好前缀化你自己的指令,比如你创建一个<carousel>指令,它可能会产生冲突,加入HTML7引入相同的元素。推荐使用两三个单词的前缀(比如btfCarousel),同样不能使用ng或者其他可能与angular未来版本起冲突的前缀。
以下的例子中,我们统一使用my前缀。
模板扩展的指令
当你有大量代表客户信息的模板。这个模板在你的代码中重复了很多次,当你改变一个地方的时候,你不得不在其他地方同时改动,这时候,你就要使用指令来简化你的模板。
我们来创建一个指令,简单的时候静态模板来替换它的内容。
<div ng-controller="Ctrl"> <div my-customer></div> </div></div>
JS
angular.module('docsSimpleDirective', []) .controller('Ctrl', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }) .directive('myCustomer', function() { return { template: 'Name: {{customer.name}} Address: {{customer.address}}' }; });</div>
注意我们这里做了一些绑定,$compile编译链接<div my-customer> </div>之后,它将会匹配子元素的指令。这意味着你可以组合一些指令。以下例子中你会看到如何做到这一点。
这个例子中,我们直接在template配置项里写上模板,但是随着模板大小的增加,这样非常不优雅。
Best Practice: 除非你的模板非常小,否则更好的是分割成单独的hmtl文件,然后使用templateUrl选项来加载。
假如你熟悉ngInclude,templateUrl跟它非常类似。现在我们使用templateUrl方式重写上面的例子:
<div ng-controller="Ctrl"> <div my-customer></div> </div></div>
JS:
angular.module('docsTemplateUrlDirective', []) .controller('Ctrl', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }) .directive('myCustomer', function() { return { templateUrl: 'my-customer.html' }; });</div>
my-customer.html
Name: {{customer.name}} Address: {{customer.address}}</div>
非常好,但是如果我们想让我们的指令匹配标签名<my-customer>? 如果我们只是简单的把<my-customer>元素放在hmtl上面,会发现没有效果。
Note: 创建指令的时候,默认仅使用属性的方式。为了创建一个能由元素名字触发的指令,你需要用到restrict配置。
restrict配置可以按如下方式设置:
-'A' 仅匹配属性名字
-'E' 仅匹配元素名字
-'AE' 可以匹配到属性名字或者元素名
所以,我们可以使用 restrict: 'E'配置我们指令。
<div ng-controller="Ctrl"> <div my-customer></div> </div></div>
JS
angular.module('docsTemplateUrlDirective', []) .controller('Ctrl', function($scope) { $scope.customer = { name: 'Naomi', address: '1600 Amphitheatre' }; }) .directive