指令:自定义HTML元素和属性
指令本质上就是AngularJS扩展具有自定义功能的HTML元素的途径。
1.HTML引导
在HTML中要用内置指令ng-app
标记出应用的根节点。这个指令需要以属性的形式来使用,因此可以将它写到任何位置,写到的开始标签上是最常规的做法。
内置指令是打包在AngularJS内部的指令。所有内置指令的命名空间都使用ng
作为前缀。为了防止命名空间冲突,不要在自定义指令前加ng
前缀。
2.第一个指令
首先我们创建一个自定义指令:
,调用指令意味着执行指令背后与之相关联的JS代码,这些代码是我们用指令定义写出来的。
在HTML里使用my-directive
声明指令,因此指令定义必须以myDirective
为名字。
angular.module('myApp',[])
.directive('myDirective', function() {
return {
restrict: 'E',
template: 'Click me'
};
});
效果如图:
通过
.directive()
方法,我们可以通过传入一个字符串和一个函数来注册一个新指令。其中字符串是这个指令的名字,指令名应该是驼峰命名风格的,函数应该返回一个对象。
directive()
方法返回的对象中包含了用来定义和配置指令所需的方法和属性。
默认情况下,AngularJS将模板生成的HTML代码嵌套在自定义标签
内部。我们可以将自定义标签从生成的DOM中完全移除掉,并只留下由模板生成的链接。将
replace
设置为
true
就可以实现这个效果,
replace
方法会用自定义元素取代指令声明,而不是嵌套在其内部。
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
template: 'Click me'
};
});
效果如图:
我们把创建的这些自定义元素称作指令(用
.directive()
方法创建),因为事实上声明指令并不需要创建一个新的自定义元素。声明指令本质上是在HTML中通过元素、属性、类或注释来添加功能。
下面都是用来声明指令的合法格式:
为了让AngularJS能够调用我们的指令,需要修改指令定义中的restrict
设置。这个设置告诉AngularJS在编译HTML时用哪种声明格式来匹配指令定义。我们可以指定一个或多个格式。可以指定以元素(E)、属性(A)、类(C)或注释(M)的格式来调用指令。
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'EAC',
replace: true,
template: 'Click me to go to Google'
};
});
无论有多少种方式可以声明指令,我们坚持使用属性方式,因为它有比较好的跨浏览器兼容性。
3.关于IE浏览器
如果用IE浏览器打开这段代码,会发现尽管指令声明了两次,但只出现了一个链接。从技术上讲,可以通过在文档头部声明新的标签来解决这个问题,但这样做的后果就是,当疏忽了一致性时会导致额外的问题。因此,最好就是始终用属性来声明指令。另外,扩展内置HTML标签,例如用AngularJS重载、
和。这些场景不会导致浏览器的兼容性问题,因为它们本身就是浏览器所支持的标签。
4.表达式
用表达式或属性调用指令的这两种情况都会在当前作用域中计算一个普通的JS表达式。
The greeting is: {{ greeting }}
用表达式来声明指令
合法的表达式声明:
我们在构造这些自定义指令时会创建新的子作用域。
向指令中传递数据
定义指令:
angular.module('myApp',[]).directive('myDirective',function() {
return {
restrict: 'A',
replace: true,
template: '{{ myLinkText }}'
}
});
在主HTML文档中,可以给指令添加myUrl
和myLinkText
两个属性,这两个参数会成为指令内部作用域的属性。
重新加载页面,声明指令的部分已经被模板代替,但是链接的href
属性是空的,并且尖括号内也没有文本。
有好几种途径可以设置指令内部作用域中属性的值。AngularJS允许通过创建新的子作用域或者隔离作用域来设置指令内部作用域中属性的值。
现在,我们在作用域对象内部把someProperty
值设置为@
这个绑定策略。这个绑定策略告诉AngularJS将DOM中some-property
属性的值复制给新作用域对象中的someProperty
属性:
scope: {
someProperty: '@'
}
注意,默认情况下someProperty
在DOM中的映射是some-property
属性。如果我们想显式指定绑定的属性名,可以用如下方式:
scope: {
someProperty: '@someAttr'
}
在这个例子中,被绑定的属性名是some-attr
而不是some-property
。
现在,当我们在指令模板或控制器中访问someProperty
时,会得到DOM属性中的值的副本:
template:'we have access to {{someProperty}}',
controller: function($scope) {
// 指令可以有它自己的控制器,在那种情况下,我们可以将
// $scope.someProperty设置成"someProperty with @ binding"
}
我们用属性将数据从DOM中复制到指令的隔离作用域中:
angular.module('myApp',[]).directive('myDirective',function() {
return {
restrict: 'A',
replace: true,
scope: {
myUrl: '@', //绑定策略
myLinkText: '@' //绑定策略
},
template: '{{myLinkText}}'
};
});
默认情况下约定DOM属性和JS中对象属性的名字是一样的。
由于作用域中属性经常是私有的,因此可以指定我们希望将这个内部属性同哪个DOM属性进行绑定。
scope: {
myUrl: '@someAttr',
myLinkText: '@'
}
上面的隔离作用域中的内容是:将指令的私有属性$scope.myUrl
同DOM中some-attr
属性的值绑定起来。这个值既可以是硬编码的也可以是当前作用域(例如Some-attr="{{expression}}
)中某个表达式的运算结果。
在DOM中要用some-attr
代替·my-url·:
更进一步,还可以在DOM对应的作用域上运算表达式,并将结果传递给指令,在指令内部最终被绑定在属性上。
在此之上,我们来看看如何创建一个文本输入域,并将输入值同指令内部隔离作用域的属性绑定起来。
这段代码是可以工作的,但如果我们将文本输入字段移到指令内部并在另一个指令中进行绑定,就无法正常工作了。
template:
''
出现这种现象的原因是,内置指令ng-model
在它自身内部的隔离作用域和DOM的作用域(由控制器提供)之间创建了一个双向数据绑定。
让我们来模仿一下这个设置过程以使例子能正工作。接下来在我们的隔离作用域和ng-model
内部的隔离作用域之间创建一个双向数据绑定。将内部的$scope.myUrl
属性同当前控制器作用域中theirUrl
属性进行绑定,在DOM中通过作用域查询来实现这个绑定。
在这个流程中,给两个方向的绑定都添加一个文本输入字段。通过这两个输入字段可以方便地观察作用域是如何在DOM中通过原型继承链接在一起的。
angular.module('myApp',[]).directive('myDirective',function() {
return {
restrict: 'A',
replace: true,
scope: {
myUrl: '=someAttr', // 经过了修改
myLinkText: '@'
},
template: '
};
});