“控制器应该尽可能保持短小精悍,而在控制器中进行DOM操作和数据操作则是一个不好的实践。设计良好的应用会将复杂的逻辑放到指令和服务中。通过使用指令和服务,我们可以将控制器重构成一个轻量且更易维护的形式” ----《AngularJs权威教程》
1、Angular Directive说明
Directive中文翻译为指令,从《AngularJs权威教程》中对指令的描述,我理解的指令是对控制器的补充,主要功能是对Dom元素和数据的操作(一般指令主Dom操作、服务主数据操作,非强制规定),本文基于Angular1.4.6版本进行讲解和演示。
在Angular中内置了一些指令,如常见的ng-app
指令初始化一个 AngularJS 应用程序,ng-model
指令把元素值(比如输入域的值)绑定到应用程序。同时Angular也支持创建自定义指令。
2、Angular Directive使用
基础案例展示。
Html代码:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body ng-controller="myCtrl">
<!-- 引入指令 -->
<my-dir></my-dir>
</body>
</html>
Js代码:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {})
.directive("myDir", function() {
return {
template: "<h1>这是自定义指令</h1>",
restrict: "E"
}
});
这是一个很简单的指令小案例,指令中的template
模板内容“这是自定义指令”通过<my-dir>
标签引入到页面中,涉及到指令两个很重要的参数template
和restrict
,下面会具体讲解指令的参数。
3、Angular Directive参数
参数 | 类型 |
---|---|
restrict | String |
priority | Number |
terminal | Boolean |
template | String or Template Function |
templateUrl | String or Template Function |
replace | Boolean or String |
transclude | Boolean |
scope | Boolean or Object |
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、scope、template、templateUrl、replace、priority、link
。
参数1:restrict
restrict用于指定directive的使用形式。如上文例子指令代码中的restrict: "E"
即指名使用Element元素形式,同时还有A、C、M
共四种形式。默认值为EA
。
参数值 | 形式 | 示例 |
---|---|---|
E | 元素 Element | <my-dir></my-dir> |
A | 属性 Attribute | <div my-dir></div> |
C | 样式 Class | <div class="my-dir"></div> |
M | 注释 Comment | <!-- directive:my-dir --> |
Html代码:
<body>
<!-- 元素形式引入指令 -->
<my-dir></my-dir>
<!-- 属性形式引入指令 -->
<div my-dir></div>
<!-- 样式形式引入指令 -->
<div class="my-dir"></div>
<!-- 指令形式引入指令 -->
<!-- directive: my-dir -->
</body>
Js代码:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {})
.directive("myDir", function() {
return {
template: "<h1>这是自定义指令</h1>",
restrict: "ECMA"
}
});
指令命名需使用驼峰模式,Html引入指令需使用“-”连接每个单词,如指令myDir
在引入指令时改为my-dir
写法。E、A、C、M
四种形式可组合使用。
说明:
在线代码演示中注释引入<!-- directive: my-dir -->
没有成功引入指令内容,这是由于在线codepen不支持,放到自己IDE中是没问题的。
参数2:scope
scope是指令的作用域,每个指令创建后可以继承父作用域(外部controller
提供的作用域或根作用域$rootScope
),也可以拥有自己独立的作用域,scope
参数有三种,分别是:false、true、{}
,默认为false
。
scope = false
当scope
设置为false
时,指令模板中可以直接使用父作用域中得变量和函数,举个例子:
Html代码:
<div ng-app="MyApp">
<div class="container" ng-controller="MyController">
<div class="my-info">我的名字是:<span ng-bind="name"></span>
<!-- 使用"ng-bind"防止网络状态不佳时出现没有被赋值表达式 -->
<br/>我的年龄是:<span ng-bind="age"></span>
</div>
<!-- 使用属性声明指令 -->
<div class="my-dir" my-dir></div>
</div>
</div>
Js代码:
angular.module("MyApp", [])
.controller("MyController", function ($scope) {
// 这里我们在作用域里初始化两个变量
$scope.name = "echeverra";
$scope.age = 20;
// 创建一个方法,修改我们创建的对象的年龄
$scope.changeAge = function () {
$scope.age = 22;
}
})
// 创建我们的指令,指令名字为"myDir"
.directive("myDir", function () {
var obj = {
// 指令的声明模式为 "AE" 属性和元素
restrict: "AE",
// 指令继承父作用域的属性和方法
scope: false,
replace: true,
template: "<div class='my-directive'>" +
"<h3>下面部分是我们创建的指令生成的</h3>" +
"我的名字是:<span ng-bind='name'></span><br/>" +
"我的年龄是:<span ng-bind='age'></span>" +
"<input type='text' ng-model='name'>"+
" </div>"
}
return obj;
});
从在线代码演示效果中我们看到创建的指令继承了父作用域controller
中的属性name、age
,同样也继承了方法changeAge
。当改变指令模块中input
的name
会发现父作用域controller
中的name
也会发生变化,这说明指令的作用域和父作用域在同一个作用域下。
scope = true
当scope
参数改为true
时,我们再来看:
同样指令中继承了父作用域的属性和方法,不同的是,当修改指令input
的name
时,指令的name
对应改变,父作用域controller
中的name
并未发生改变,这说明指令的作用域和父作用域不在同一个作用域下。
scope = {}
当scope
设置为{}
会使指令的作用域变得更加灵活,修改之前的代码:
Html代码:
<div ng-app="MyApp">
<div class="container" ng-controller="MyController">
<div class="my-info">我的名字是:<span ng-bind="name"></span>
<br/>我的年龄是:<span ng-bind="age"></span>
<br />
</div>
<div class="my-dir" my-dir my-name="{{name}}" age="age" change-my-age="changeAge()"></div>
</div>
</div>
Js代码:
angular.module("MyApp", [])
.controller("MyController", function ($scope) {
$scope.name = "echeverra";
$scope.age = 20;
$scope.male = 'man';
$scope.changeAge = function(){
$scope.age = 0;
}
})
.directive("myDir", function () {
var obj = {
restrict: "AE",
scope: {
name: '@myName',
age: '=',
changeAge: '&changeMyAge'
},
replace: true,
template: "<div class='my-dir'>" +
"<h3>下面部分是我们创建的指令生成的</h3>" +
"我的名字是:<span ng-bind='name'></span><br/>" +
"我的性别是:<span ng-bind='male'></span><br/>" +
"我的年龄是:<span ng-bind='age'></span><br/>" +
"在这里修改名字:<input type='text' ng-model='name'><br/>" +
"<button ng-click='changeAge()'>修改年龄</button>" +
" </div>"
}
return obj;
});
我们使用了隔离的作用域,不代表我们不可以使用父作用域的属性和方法,我们可以通过向scope
的{}
中传入特殊的前缀标识符(即prefix
),来进行数据绑定。
我们可以通过前缀标识符@, &, =
应用到指令中的属性,如:{name: '@myName', age: '=', changeAge: '&changeMyAge'}
(age: '='
是age: '=age'
的简写),我们可以在<div class="my-directive" my-directive my-name="{{name}}" age="age" change-my-age="changeAge()"></div>
这个元素中,通过使用属性my-name,age,change-my-age
来引用这些属性的值。
首先可以确定的是scope={}
不会继承父作用域,因为我的性别项male
值是空的,在scope
中未进行属性male
绑定。
那么前缀标识符@, &, =
分别有什么作用呢?
@
这是一个单向数据绑定的前缀标识符。在元素中使用属性,如:<div my-dir my-name="{{name}}"></div>
,属性的名字要用“-”将两个单词连接,因为是数据的单向绑定所以要通过使用{{}}
来绑定数据。在线代码演示结果中修改input
的name
值可看出,只指令的作用域生效name
发生改变,父作用域name
未改变,这点和scope
参数为true
效果是一样的。
=
这是一个双向数据绑定的前缀标识符。在元素中使用属性,如:<div my-dir age="age"></div>
,因为是数据的双向绑定,所以使用=
前缀实现,而不是{{}}
。在线代码演示结果中修改input
的name
值可看出,指令的作用域和父作用域name
均发生改变,这点和scope
参数为false
效果是一样的。
&
这是一个绑定函数方法的前缀标识符。在元素中使用属性,如:<div my-dir change-my-age="changeAge()"></div>
,属性的名字要用“-”将每个单词连接。在线代码演示结果中点击“修改年龄”按钮,指令作用域和父作用域中的年龄都发生改变,这点和前缀标识符=
效果相同。
注意:
在新创建指令的作用域对象中,使用属性的名字进行绑定时,要使用驼峰命名标准,如下面的代码:
scope: {
// `myName` 就是原来元素中的`my-name`属性
name: '@myName',
age: '=',
// `changeMyAge`就是原来元素中的`change-my-age`属性
changeAge: '&changeMyAge'
}
进一步说明,我们创建的指令是如何利用这些前缀标识符来寻找我们想要的属性或函数的呢?
使用@
时当指令编译到模板的name
,就会到scope
中查找是否含有name
的键值对(name:'@myName'
),找到后发现前缀标识符@
就知道这是一个单向数据绑定标识符,之后会去寻找元素上含有这个值的属性(my-name={{name}}
),然后在父作用域中查找{{name}}
的值,得到之后传递给属性my-name
,通过指令参数scope
中键值对name:'@myName'
最后传给了模板中的name
。(此处比较绕,不过真心值得研究一番,有助于深刻理解)。=
和&
与@
差不多,只不过=
进行的是双向的数据绑定,不论模板还是父作用域上的属性的值发生改变都会使另一个值发生改变,而&
是绑定函数而已。
总结: scope = false
,scope = true
初始化都会继承父作用域中的属性和方法,不同的是scope = false
和父作用域在同一作用域下,scope = true
和父作用域不在同一作用域,而是创建了一个独立的作用域。scope = {}
不会继承父作用域,属性控制更加灵活,单向绑定@
,外部scope
能够影响内部scope
,但反过来不成立,双向绑定=
,外部scope
和内部scope
的model
能够相互改变,函数绑定&
把内部scope
的函数和外部scope
的函数绑定起来。
参数3:template
template
参数值可以是一个字符串也可以是一个函数,值为字符串比较好理解,上文一直在演示的就是值为字符串的形式。当值为函数时,可接收两个参数element
和attrs
。举例:
Html代码:
<!-- 引入指令 -->
<my-dir name="echeverra" age="20" title="指令元素"></my-dir>
Js代码:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {})
.directive("myDir", function() {
return {
template: function(element, attrs) {
return '<h2>name:'+attrs.name+'</h2>'+
'<h2>age:'+attrs.age+'</h2>'+
'<h2>title:'+attrs.title+'</h2>'
},
restrict: "E"
}
});
函数参数element
是指使用此指令的元素<my-dir name="echeverra" age="20" title="指令元素"></my-dir>
,而attrs
是指令元素上所有属性的集合,形如:
{
name: 'echeverra',
age: '20',
title: '指令元素'
}
参数4:templateUrl
templateUrl
和上一个参数template
用法基本相同,只不过template
值为字符串是一段html模板,templateUrl
值为字符串是文件路径。如:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {})
.directive("myDir", function() {
return {
templateUrl: 'index.html';
}
});
具体使用请参考参数template
。
参数5:replace
replace
是一个可选参数,默认值为false
,表示指令中模板template
内容会当做子元素插入到指令元素中,如果设置为true
,表示指令中模板会直接替换掉指令元素。
举例replace
为false
:
Html代码:
<div id="dir">
<!-- 引入指令 -->
<my-dir></my-dir>
</div>
<button ng-click='getDirCont()'>获取引入指令元素</button>
<div ng-bind="dirCont"></div>
Js代码:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {
$scope.getDirCont = function() {
$scope.dirCont = document.getElementById('dir').innerHTML;
}
})
.directive("myDir", function() {
return {
template:"<h1>这是自定义指令</h1>",
restrict: "E",
replace: false,
}
});
点击获取引入指令元素按钮后显示<my-dir><h1>这是自定义指令</h1></my-dir>
,可以看到指令元素<my-dir></my-dir>
还在,如果将replace
设置为true
,我们在来看:
这次获取只显示元素<h1>这是自定义指令</h1>
,说明指令元素<my-dir></my-dir>
被替换掉了,这就是两者的区别。
参数6:priority
当同一个元素声明两个指令,需要设定执行先后顺序,这时就需要用到参数priority
,值为数字,默认值为0,我们来看下不使用priority
的情况会有什么麻烦。
Html代码:
<!-- 引入指令 -->
<div ng-init="greeting='Hello '" d1 d2>{{greeting}}!</div>
Js代码:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {})
.directive("d1", function() {
return {
link: function (scope) {
scope.greeting += 'World ';
}
}
})
.directive("d2", function() {
return {
link: function (scope) {
scope.greeting += 'Angular ';
}
}
});
输出:Hello Angular World !
,可见先执行了ng-init="greeting='Hello '"
,然后执行了指令d2
,最后是d1
。实际我们想预期输出的是Hello World Angular !
,我们将指令元素中的d1、d2
顺序对调,再将指令中创建的d1、d2
两个指令顺序对调,再试发现输出的还是Hello Angular World !
,这是因为Angular
是使用字母顺序来确定链接函数谁先被调用,将d1
改为e1
就会得到预期的结果,有兴趣的可以尝试一下。如何设定指令执行优先级?这时候priority
就派上用场了。
Js代码:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {})
.directive("d1", function() {
return {
priority: 1,
link: function (scope) {
scope.greeting += 'World ';
}
}
})
.directive("d2", function() {
return {
priority: 2,
link: function (scope) {
scope.greeting += 'Angular ';
}
}
});
输出:Hello World Angular
。
参数7:link
link
函数主要用于操作Dom
元素,给Dom
元素绑定事件和监听,link
参数要求声明一个函数,称之为链式函数。
写法:
link: function(scope, element, attrs) {
// 在这里操作DOM
}
-
scope
:指令所在的作用域 -
element
:指令元素的封装,可以调用angular封装的简装jq方法和属性 -
attr
:指令元素的属性的集合
如果指令使用了require选项,那么链接函数会有第四个参数,代表控制器或者所依赖的指令的控制器。
require 'SomeController',
link: function(scope, element, attrs, SomeController) {
// 在这里操作DOM,可以访问require指定的控制器
}
下面举例操作Dom
元素,改变元素样式。
Html代码:
<!-- 引入指令 -->
<input ng-model="color" placeholder="请输入颜色值"/>
<br/>
<my-dir name="my-dir-name"></my-dir>
Js代码:
angular.module('myApp', [])
.controller("myCtrl", function($scope) {})
.directive("myDir", function() {
return {
template:'<h3 style="background-color:{{color}}">code</h3>',
replace:true,
link:function(scope, ele, attrs, ctrl) {
ele.bind('click', function () {
scope.$apply(function () {
scope.color = 'red';
ele.text(attrs.name);
})
});
ele.bind('mouseover', function () {
ele.css({'cursor': 'pointer'})
});
}
}
});
点击code
,获取指令元素的属性name
值,ele
调用text()
展示,同时ele
绑定bind
鼠标悬浮事件,调用css()
方法,改变鼠标样式。ele
是指令元素的jqLite包装,所以可以基本的jq
方法,如例子中的bind()
,css()
等。
结语:
还有一些其他不常见的属性,本文没有介绍。如有错误,欢迎指出,感谢以下博文。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。