Angularjs 指令详解

引言

  • angularjs有许多内置指令,这些指令主要用于操纵DOM、指定路由、绑定事件处理器、执行数据绑定等等;
  • angularjs不仅有丰富的内置指令,而且可以自定义指令,实现自己需要的功能;
  • angularjs指令系统是angularjs最重要的一个知识点,在本文中加以总结,如有不完善之处请您指出;
  • 非常感谢慕课网的大漠穷秋老师,本文的很多代码复制自大漠穷秋老师在慕课网angularjs课程的示例代码;

内置指令

in-build-directive

编写指令

  • 最简单的指令模板如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en" ng-app="testApp">
<head>
<meta charset="UTF-8">
<title></title>

<link rel="stylesheet" type="text/css" href="lib/bootstrap-3.3.1/css/bootstrap.min.css">
</head>
<body>
<div ng-controller="testCtrl">
<hello></hello>
<div hello></div>
<div class="hello"></div>
<!-- directive:hello -->
</div>

<script src="lib/bootstrap-3.3.1/js/bootstrap.min.js"></script>
<script src="lib/angular-1.3.3/angular.min.js"></script>
<script src="test.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
//test.js
var mod = angular.module('testApp', []);

mod.directive('hello',function(){
return{
restrict:'AEMC',
template:'<div>this is a directive test!</div>',
replace:true
}
});
  • 自定义指令参数

directive-params

  • 对自定义指令声明参数的说明:
    • restrict:替换自定义元素类型,E(元素)、C(类)、A(属性)、M(注释);
    • template:替换的模板,如template: <div>this is a directive test.</div>;
    • templateURL: 替换的模板的URL,如templateUrl: 'hello.html';
    • replace:true,将自定义标签的内容全部替换掉;
    • transclude:true,自定义标签的内容不会被替换,而是被放置在template中的<div ng-transclude></div>中;
    • compile: function(){...},自定义compile函数,一般放置暴露给外部看的(比如其他directive)一些公共函数;
    • link: function(){...},自定义link函数,一般放置本directive中操纵DOM、添加事件监听器等的代码;
    • 作用域问题:在上述代码中,四个被指令替换的DOM标签其实所在的是同一个作用域,这样做不太安全,如果需要将作用域分开,则在指令中添加scope:{}即可,下面会详细讲解;

独立scope

  • 实现独立scope的方法是在指令中添加scope:{}即可;
  • 代码如下,改动任意一个input标签的内容,其他的directive不受影响,即directive作用域独立:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<hello></hello>
<hello></hello>
<hello></hello>
<hello></hello>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="IsolateScope.js"></script>
</html>
1
2
3
4
5
6
7
8
9
10
//IsolateScope.js
var myModule = angular.module("MyModule", []);
myModule.directive("hello", function() {
return {
restrict: 'AE',
scope:{},
template: '<div><input type="text" ng-model="userName"/>{{userName}}</div>',
replace: true
}
});

指令和controller的交互

  • 在link函数中完成简单的directive和controller的交互,下面程序实现了在link中添加事件监听器并调用controller的特定方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/ng-table.css">
<style>
</style>

</head>
<body ng-app="myApp">

<div ng-controller="myCtrl">
<hello>滑动加载</hello>
</div>

<script src="js/jquery-2.1.4.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/ng-table.js"></script>
<script src="app.js"></script>

<script>

</script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//app.js
var mod = angular.module('myApp', []);

mod.controller("myCtrl",function($scope){
$scope.loadData = function(){
console.log("正在加载数据");
}
});

mod.directive('hello',function(){
return{
restrict:'AEMC',
link: function(scope, element, attr){
element.bind("mouseenter",function(){
scope.loadData();
});
}
}
});
  • 一个指令也可同时调用多个controller的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/ng-table.css">
<style>
</style>

</head>
<body ng-app="myApp">

<div ng-controller="myCtrl">
<hello howtoload="loadData()">滑动加载</hello>
</div>

<div ng-controller="myCtrl2">
<hello howtoload="loadData2()">滑动加载2</hello>
</div>

<script src="js/jquery-2.1.4.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/ng-table.js"></script>
<script src="app.js"></script>

<script>

</script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//app.js

var mod = angular.module('myApp', []);

mod.controller("myCtrl",function($scope){
$scope.loadData = function(){
console.log("正在加载数据");
}
});

mod.controller("myCtrl2",function($scope){
$scope.loadData2 = function(){
console.log("正在加载数据...2222");
}
});

mod.directive('hello',function(){
return{
restrict:'AEMC',
link: function(scope, element, attrs){
element.bind("mouseenter",function(event){
scope.$apply(attrs.howtoload); //一律小写,不用驼峰法则,没有括号()
});
}
}
});

指令间通信

  • 指令间通信的方法是:scope: {}定义独立作用域,controller暴露方法给别的指令,require引用其他指令;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!doctype html>
<html ng-app="MyModule">

<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="Directive&Directive.js"></script>
</head>

<body>
<div class="row">
<div class="col-md-3">
<superman strength>动感超人---力量</superman>
</div>
</div>
<div class="row">
<div class="col-md-3">
<superman strength speed>动感超人2---力量+敏捷</superman>
</div>
</div>
<div class="row">
<div class="col-md-3">
<superman strength speed light>动感超人3---力量+敏捷+发光</superman>
</div>
</div>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//Directive&Directive.js

var myModule = angular.module("MyModule", []);
myModule.directive("superman", function() {
return {
scope: {},
restrict: 'AE',
controller: function($scope) {
$scope.abilities = [];
this.addStrength = function() {
$scope.abilities.push("strength");
};
this.addSpeed = function() {
$scope.abilities.push("speed");
};
this.addLight = function() {
$scope.abilities.push("light");
};
},
link: function(scope, element, attrs) {
element.addClass('btn btn-primary');
element.bind("mouseenter", function() {
console.log(scope.abilities);
});
}
}
});
myModule.directive("strength", function() {
return {
require: '^superman',
link: function(scope, element, attrs, supermanCtrl) {
supermanCtrl.addStrength();
}
}
});
myModule.directive("speed", function() {
return {
require: '^superman',
link: function(scope, element, attrs, supermanCtrl) {
supermanCtrl.addSpeed();
}
}
});
myModule.directive("light", function() {
return {
require: '^superman',
link: function(scope, element, attrs, supermanCtrl) {
supermanCtrl.addLight();
}
}
});

scope绑定

  • @绑定:将当前属性作为字符串传递,实现绑定;
    • 下面代码中,flavor:'@'实现的功能可以由注释掉的link函数代替;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<div ng-controller="MyCtrl">
<drink flavor="{{ctrlFlavor}}"></drink>
</div>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="ScopeAt.js"></script>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myModule = angular.module("MyModule", []);
myModule.controller('MyCtrl', ['$scope', function($scope){
$scope.ctrlFlavor="百威";
}])
myModule.directive("drink", function() {
return {
restrict:'AE',
scope:{
flavor:'@'
},
template:"<div>{{flavor}}</div>"
// ,
// link:function(scope,element,attrs){
// scope.flavor=attrs.flavor;
// }
}
});
  • =实现与父scope的属性双向绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<div ng-controller="MyCtrl">
Ctrl:
<br>
<input type="text" ng-model="ctrlFlavor">
<br>
Directive:
<br>
<drink flavor="ctrlFlavor"></drink>
</div>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="ScopeEqual.js"></script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
var myModule = angular.module("MyModule", []);
myModule.controller('MyCtrl', ['$scope', function($scope){
$scope.ctrlFlavor="百威";
}])
myModule.directive("drink", function() {
return {
restrict:'AE',
scope:{
flavor:'='
},
template:'<input type="text" ng-model="flavor"/>'
}
});
  • &:传递一个父scope的函数,稍后调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<div ng-controller="MyCtrl">
<greeting greet="sayHello(name)"></greeting>
<greeting greet="sayHello(name)"></greeting>
<greeting greet="sayHello(name)"></greeting>
</div>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="ScopeAnd.js"></script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var myModule = angular.module("MyModule", []);
myModule.controller('MyCtrl', ['$scope', function($scope){
$scope.sayHello=function(name){
alert("Hello "+name);
}
}])
myModule.directive("greeting", function() {
return {
restrict:'AE',
scope:{
greet:'&'
},
template:'<input type="text" ng-model="userName" /><br/>'+
'<button class="btn btn-default" ng-click="greet({name:userName})">Greeting</button><br/>'
}
});

angularjs 初始化过程

  • 理解angularjs的初始化过程有助于我们进一步理解link和compile函数;
  • 初始化过程如下:
    • ng-app寻找angular作用边界,在ng-app作用域范围内创建$rootScope,以后各层级的$scope均在$rootScope基础上创建,实现树形作用域结构;
    • 解析module中的各种组件名称对应的回调函数的注册表,回调函数不被执行,注册表中的组件均可被用于依赖注入;
    • controller立即执行;
    • Compile:根据从服务器获取的或者dierective中的HTML模板编译HTML,对指定的模板进行转换,生成Linking函数,此时DOM是静态DOM;
    • Link:将Compile结果与Scope结合,产生动态视图,包括在元素上注册时间监听,即动态DOM;
    • 对于同一个指定的多个实例(如ng-repeat),compile只会执行一次,而link对于指令的每个实例都会执行一次;
您的支持是对我最大的鼓励!