指令的编译(compile)和link过程分为:
-
compile
阶段 -
link
阶段,这个阶段又分为:-
controller
阶段 -
pre link
阶段 -
post link
阶段 (也称为linking
)
-
对单一指令的过程如下图:
可以看出各个阶段的特点:
Compile
这个阶段可以有2个参数: tElement
和 tAttributes
,(t
表示template模版的意思)下面是这个阶段的特点:
- 加载和遍历模版DOM
- 只执行一次
- 返回link函数,或一个对象(包含
pre
,post(或者称为link)
) - 不存在 Scope
- 模版实例或克隆模版还未被创建
- 可以做一些所有实例或克隆模版能共享的操作
- 能够操作DOM模版
- 不能使用克隆的数据或事件
- 不能获取克隆的DOM
Controller
这个阶段执行指令自身控制器,这个阶段有如下特点:
- 第一个执行实例或克隆的模版,一般使用
$element
,$attributes
,$scope
命名 - 创建Scope(scope的初始化)
- 能够操作模版实例的数据
- 不推荐操作实例DOM
Pre
这个阶段一般使用 iElement
, iAttributes
, scope
命名(i
表示instance实例的意思),这个阶段有如下特点:
- 对每个实例,controller阶段完成之后这个阶段立刻执行(后面嵌套指令会谈到)
- 可以对DOM模版进行引用
- 实例作用域已准备
- 实例还没有linked到作用域,因此不能设置绑定(bindings)
- 子元素或指令还没有准备好(后面嵌套指令会谈到)
- 能够操作Scope
- 在这个阶段设置数据很安全,甚至可以设置子元素数据
- 操作DOM实例还不是很安全
- 不能访问子元素
Post
这个阶段相当于通常写的 link
函数,有如下特点:
- 每个实例的最后一个阶段,也称之为
linkng
或者link
函数 - 可以对DOM模版进行引用
- 作用域和实例linked(和数据绑定)
- 子元素或子指令已经准备好(或者说已经linked)
- 能够操作Scope
- 添加事件,观察子元素都很安全
- 能安全的操作DOM实例
- 设置子元素数据还不是很安全
上面几个阶段就是指令被编译和link的过程,示例如下:
// html
<div my-directive text="first"></div>
// app.js
angular
.module('app', [])
.directive('myDirective', function() {
return {
// 1. 编译阶段 第1步
// 注意参数 'tElement'的 't' 表示 模版的意思
compile: function(tElement, tAttributes) {
console.log(tAttributes.text + ' - compile 阶段');
// 返回一个对象(pre和post) 或 函数(link函数)
return {
// 3. 可以访问scope 第3步
// 注意参数 'iElement'的 'i' 表示 实例的意思
pre: function(scope, iElement, iAttributes) {
console.log(iAttributes.text + ' - pre 阶段');
},
// 4. 相当于link函数 第4步
post: function(scope, iElement, iAttributes) {
console.log(iAttributes.text + ' - post 阶段');
}
};
},
// 2. controller阶段 第2步 注意参数
controller: function($scope, $element, $attrs) {
console.log($attrs.text + ' - controller 阶段');
}
}
})
// 最后输出结果为
first - compile 阶段
first - controller 阶段
first - pre 阶段
first - post 阶段
另一示例:
// html
<div my-directive text="{{i}}" ng-repeat="i in [1, 2, 3]"></div>
// js 同上
// 最后输出结果为
{{i}} - compile 编译
{{i}} - controller 编译 // {{i}} 没有被计算
1 - pre 编译
1 - post 编译
{{i}} - controller 编译
2 - pre 编译
2 - post 编译
{{i}} - controller 编译
3 - pre 编译
3 - post 编译
可以看出controller阶段,i的值还没有计算,如果想要i在controller阶段被计算出来,则需要使用 $interpolate
(插值服务)
// html 同上
// js
angular
.module('app', [])
.directive('myDirective', function($interpolate) { // 引入$interpolate服务
return {
compile: function(tElement, tAttributes) {
console.log(tAttributes.text + ' - compile 阶段');
return {
pre: function(scope, iElement, iAttributes, controller) {
console.log(iAttributes.text + ' - pre 阶段');
},
post: function(scope, iElement, iAttributes, controller) {
console.log(iAttributes.text + ' - post 阶段');
}
};
},
controller: function($scope, $element, $attrs) {
// 使用插值服务计算 '{{i}}' 的值
// 注意后面的 $scope 表示对该作用域
var v = $interpolate($attrs.text)($scope);
console.log(v + ' - controller 阶段');
}
}
})
// 最后输出结果为
{{i}} - compile 编译
1 - controller 编译
1 - pre 编译
1 - post 编译
2 - controller 编译
2 - pre 编译
2 - post 编译
3 - controller 编译
3 - pre 编译
3 - post 编译
对于嵌套指令,内部执行过程如下图
看着数字走,即是嵌套指令编译和link的完整过程
示例:
// html
<div my-directive text="first">
<div my-directive text="..second">
<div my-directive text="...three"></div>
</div>
</div>
// js代码同上
// 输出结果
first - compile 阶段 // 从外到内
..second - compile 阶段
...three - compile 阶段
first - controller 阶段 // 从外到内
first - pre 阶段
..second - controller 阶段
..second - pre 阶段
...three - controller 阶段
...three - pre 阶段
...three - post 阶段 // 从内到外
..second - post 阶段
first - post 阶段
可以看出这个过程和图中的过程是一致的:
- 首先从外到内进行编译阶段
- 然后从外到内进行(controller阶段 -> pre阶段)这是一个整体
- 最后从内到外完成post阶段,即从子元素或子指令最先开始link
compile 的几种写法
如果值希望使用compile 和 post, controller
.directive('myDirective', function($interpolate) {
return {
compile: function(tElement, tAttributes) {
console.log(tAttributes.text + ' - compile 阶段');
// 给模版添加样式,则后面的实例都可以使用
tElement.css('border', '1px solid black');
// 表示post函数
return function(scope, iElement, iAttributes, controller) {
console.log(iAttributes.text + ' - post 阶段');
if (iAttributes.text === '3') {
// 对text值为3的元素添加背景颜色
iElement.css('background-color', 'green');
}
};
},
controller: function($scope, $element, $attrs) {
var v = $interpolate($attrs.text)($scope);
console.log(v + ' - controller 阶段');
}
}
})
如果希望保留pre 和 post, controller, 而不需要compile
这种情况可以返回一个link 对象,即可:
.directive('myDirective', function($interpolate) {
return {
link: {
pre: function(scope, iElement, iAttributes, controller) {
console.log(iAttributes.text + ' - pre 阶段');
},
post: function(scope, iElement, iAttributes, controller) {
console.log(iAttributes.text + ' - post 阶段');
if (iAttributes.text === '3') {
// 对text值为3的元素添加背景颜色
iElement.css('background-color', 'green');
iElement.on('click', scope.handleClick);
}
},
controller: function($scope, $element, $attrs) {
var v = $interpolate($attrs.text)($scope);
console.log(v + ' - controller 阶段');
// listener handler
$scope.handleClick = function() {
alert($attrs.text);
}
}
}
})
如果只需要post函数,不需要controller等
.directive('myDirective', function() {
// 表示post函数
return function(scope, iElement, iAttributes, controller) {
console.log(iAttributes.text + ' - post 阶段');
if (iAttributes.text === '3') {
// 对text值为3的元素添加背景颜色
iElement.css('background-color', 'green');
}
})
总结
理解这一过程对理解指令是如何运作的使用有帮助。具体youtube - Compile and Link in depth