指令(Directive),是AngularJS中一块比较重要的内容,在实践中,我们提倡将控制器(Controller)写得短小精悍,逻辑代码尽量少,要实现这种目的,我们主要通过将逻辑代码抽取到服务(Service)和指令中实现。
今天主要介绍一下指令的相关内容。
一、作用、目的
指令,本质上就是AngularJS扩展具有自定义功能的HTML元素的途径,AngularJS已经实现了很多内置指令,我们也可以通过自定义指令定制自己所需要的HTML元素,从而简化应用,并使自己的架构、代码更优雅,另一方面,由于AngularJS拥有自己的生命周期,很多插件在AngularJS应用中往往会显示“不好用”,这时候,就需要我们自己实现指令,对插件进行集成,例如,在工作中我们团队就对echarts和highcharts进行了集成。
二、定义语法
在AngularJS中,允许我们使用.directive() 方法定义指令, 我们可以通过传入一个字符串和一个函数来注册一个新指令。其中字符串是这个指令的名字,指令名应该是驼峰命名风格的,函数应该返回一个对象,该对象定义了我们指令的细节。
注:1、驼峰命名风格用来将一个短语写在一个单词中,除了第一个单词外其他单词首字母大写,中间不加空格。例如, cute boy 用驼峰风格来写应该是cuteBoy。
注:2、在HTML里使用 my-directive声明指令,指令定义必须以myDirective为名字。如下
<my-directive></my-directive> angular.module('myApp', []) .directive('myDirective', function() {
// 指令定义对象 return { // 省略... });
directive() 方法可以接受两个参数:
1. name(字符串)
指令的名字,用来在视图中引用特定的指令。
2. factory_function (函数)
这个函数返回一个对象,其中定义了指令的全部行为。
对象的可选设置如下:
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: String,
priority: Number,
terminal: Boolean,
template: String or Template Function:
function(tElement, tAttrs) (...},
templateUrl: String,
replace: Boolean or String,
scope: Boolean or Object,
transclude: Boolean,
controller: String or
function(scope, element, attrs, transclude, otherInjectables) { ... },
controllerAs: String,
require: String,
link: function(scope, iElement, iAttrs) { ... },
compile: // 返回一个对象或连接函数,如下所示:
function(tElement, tAttrs, transclude) {
return {
pre: function(scope, iElement, iAttrs, controller) { ... },
post: function(scope, iElement, iAttrs, controller) { ... }
}
// 或者
return function postLink(...) { ... }
}
};
});
- restrict(字符串)
restrict是一个可选的参数。它告诉AngularJS这个指令在DOM中可以何种形式被声明。默认AngularJS认为restrict的值是A,即以属性的形式来进行声明。
可选值如下:
E(元素) <my-directive></my-directive>
A(属性,默认值) <div my-directive="expression"></div>
C(类名) <div class="my-directive:expression;"></div>
M(注释) <--directive:my-directive expression-->
这些选项可以单独使用,也可以混合在一起使用:restrict: 'EA' // 输入元素或属性
属性是用来声明指令最常用的方式,因为它能在包括老版本的IE浏览器在内的所有浏览器中正常工作,并且不需要在文档头部注册新的标签。
注:1、尽量避免用注释方式来声明属性。这种方式最初被用来声明由多个标签组成的指令。这种方法在某些情况下特别有用,比如在<table> 元素内使用 ng-repeat指 令 , 但 在 AngularJS 1.2 中 ng-repeat 可 以 通 过 ng-repeat-start 和ng-repeat-end来更优雅地满足这个需求,注释模式就没有什么用武之地了。
- priority(数值型)
优先级参数可以被设置为一个数值。大多数指令会忽略这个参数,使用默认值0,但也有些场景设置高优先级是非常重要甚至是必须的。优先级高的指令先于其他指令调用。如果一个元素上具有两个优先级相同的指令,声明在前面的那个会被优先调用。
- terminal (布尔型)
terminal 是一个布尔型参数,可以被设置为true或false。这个参数用来告诉AngularJS停止运行当前元素上比本指令优先级低的指令。但同当前指令优先级相同的指令还是会被执行。
- template(字符串或函数)
template参数是可选的,必须被设置为以下两种形式之一:
一段HTML文本;
一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个代表模板的字符串。 tElement和tAttrs中的t代表template,是相对于instance的。模板元素或属性与实例元素或属性之间是有区别的。AngularJS会同处理HTML一样处理模板字符串。模板中可以通过大括号标记来访问作用域,例如{{ expression }} 。如果模板字符串中含有多个DOM元素,或者只由一个单独的文本节点构成,那它必须被包含在一个父元素内。换句话说,必须存在一个根DOM。
template: '\ <div> <-- single root element -->\ <a href="http://google.com">Click me</a>\ <h1>When using two elements, wrap them in a parent element</h1>\ </div>\
注意每一行末尾的反斜线,这样AngularJS才能正确解析多行字符串。在实际生产中,更好的选择是使用templateUrl参数引用外部模板,因为多行文本阅读和维护起来都是一场噩梦。
- templateUrl (字符串或函数)
templateUrl 是可选的参数,可以是以下类型:
一个代表外部HTML文件路径的字符串;
一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个外部HTML文件路径的字符串。
无 论 哪 种 方 式 , 模 板 的 URL 都 将 通 过 AngularJS 内 置 的 安 全 层 , 特 别 是 $getTrustedResourceUrl ,这样可以保护模板不会被不信任的源加载。
默认情况下,调用指令时会在后台通过Ajax来请求HTML模板文件。有两件事情需要知道。
在本地开发时,需要在后台运行一个本地服务器,用以从文件系统加载HTML模板,否则会导致Cross Origin Request Script( CORS)错误。
模板加载是异步的,意味着编译和链接要暂停,等待模板加载完成。通过Ajax异步加载大量的模板将严重拖慢一个客户端应用的速度。为了避免延迟,可以在部署应用之前对HTML模板进行缓存。在大多数场景下缓存都是一个非常好的选择,因为AngularJS通过减少请求数量提升了性能。模板加载后, AngularJS会将它默认缓存到$templateCache服务中。在实际生产中,可以提前将模板缓存到一个定义模板的JavaScript文件中。
- replace(布尔型)
replace是一个可选参数,如果设置了这个参数,值必须为true,因为默认值为false。默认值意味着模板会被当作子元素插入到调用此指令的元素内部。
- scope参数(布尔型或对象)
scope参数是可选的,可以被设置为true或一个对象。默认值是false。
当scope设置为true时,会从父作用域继承并创建一个新的作用域对象。
当scope设置为一个对象,则创建了一个隔离作用域。如果一个元素上有多个指令使用了隔离作用域,其中只有一个可以生效。只有指令模板中的根元素可以获得一个新的作用域。因此,对于这些对象来说scope默认被设置为true。
AngularJS提供了几种方法能够将指令内部的隔离作用域,同指令外部的作用域进行数据绑定。
@ (or @attr)@ (or @attr)@本地作用域属性:使用@符号将本地作用域同DOM属性的值进行绑定。指令内部作用域可以使用外部作用域的变量:
=双向绑定:通过=可以将本地作用域上的属性同父级作用域上的属性进行双向的数据绑定。就像普通的数据绑定一样,本地属性会反映出父数据模型中所发生的改变。
&父级作用域绑定 通过&符号可以对父级作用域进行绑定,以便在其中运行函数。意味着对这个值进行设置时会生成一个指向父级作用域的包装函数。
- transclude(布尔型)
transclude(嵌入)是一个可选的参数。如果设置了,其值必须为true,它的默认值是false。用以包含任意内容。
我们可以将整个模板,包括其中的指令通过嵌入全部传入一个指令中。这样做可以将任意内容和作用域传递给指令。 transclude参数就是用来实现这个目的的,指令的内部可以访问外部指令的作用域,并且模板也可以访问外部的作用域对象。
注:1、只有当你希望创建一个可以包含任意内容的指令时, 才使用 transclude: true。
注:2、如果指令使用了transclude参数,那么在控制器中就无法正常监听数据模型的变化了。这就是最佳实践总是建议在链接函数里使用$watch服务的原因。
未完待续