Angular指令 – 何时以及如何使用编译,控制器,前链接和后链接
在编写Angular指令时,可以使用以下任何函数来操作DOM行为,声明指令的元素的内容和外观:
- 编
- 调节器
- 前链路
- 后链接
应该使用哪个function似乎有些困惑。 这个问题包括:
指令基础
- 如何声明各种function?
- 源模板和实例模板有什么区别?
- 指令函数以何种顺序执行?
- 这些函数调用之间还会发生什么?
function性质,做和不做
- 编
- 调节器
- 预链接
- post链接
相关问题:
- 指令:链接vs编译与控制器 。
- 定义angular.js指令时,“控制器”,“链接”和“编译”函数之间的区别 。
- 在angularjs中编译和链接函数有什么区别 。
- AngularJS指令中的预编译和后编译元素之间的区别? 。
- angularJS指令 – 模板,编译或链接? 。
- 张贴链接vs Angular js指令中的前链接 。
指令函数以何种顺序执行?
对于单个指令
基于下面的plunk ,考虑下面的HTML标记:
<body> <div log='some-div'></div> </body>
通过以下指令声明:
myApp.directive('log', function() { return { controller: function( $scope, $element, $attrs, $transclude ) { console.log( $attrs.log + ' (controller)' ); }, compile: function compile( tElement, tAttributes ) { console.log( tAttributes.log + ' (compile)' ); return { pre: function preLink( scope, element, attributes ) { console.log( attributes.log + ' (pre-link)' ); }, post: function postLink( scope, element, attributes ) { console.log( attributes.log + ' (post-link)' ); } }; } }; });
控制台输出将是:
some-div (compile) some-div (controller) some-div (pre-link) some-div (post-link)
我们可以看到compile
首先执行,然后是controller
,然后是pre-link
,最后是post-link
。
对于嵌套的指令
注意:以下内容不适用于使子女处于链接function的指令。 很多Angular指令都是这样做的(比如ngIf,ngRepeat或者带有transclude的指令)。 这些指令将在调用它们的子指令之前本地调用其
link
函数。
原始的HTML标记通常由嵌套的元素组成,每个元素都有自己的指令。 就像在下面的标记中(见plunk ):
<body> <div log='parent'> <div log='..first-child'></div> <div log='..second-child'></div> </div> </body>
控制台输出将如下所示:
// The compile phase parent (compile) ..first-child (compile) ..second-child (compile) // The link phase parent (controller) parent (pre-link) ..first-child (controller) ..first-child (pre-link) ..first-child (post-link) ..second-child (controller) ..second-child (pre-link) ..second-child (post-link) parent (post-link)
我们可以在这里区分两个阶段 – 编译阶段和链接阶段。
编译阶段
当DOM被加载的时候,Angular开始了编译阶段,它从上到下遍历标记,并且在所有的指令上调用compile
。 在graphics上,我们可以这样expression:
可能很重要的一点是,在这个阶段,编译function获得的模板是源模板(不是实例模板)。
链接阶段
DOM实例通常只是源模板被渲染到DOM的结果,但是它们可能是由ng-repeat
创build的,或者是实时引入的。
每当具有指令的元素的新实例呈现给DOM时,链接阶段开始。
在这个阶段,Angular呼叫controller
, pre-link
,迭代子节点,并在所有的指令中调用post-link
,如下所示:
这些函数调用之间还会发生什么?
各种指令函数是在两个其他angular度函数中执行的,这两个函数称为$compile
(其中指令的compile
被执行)和一个名为nodeLinkFn
(指令的controller
, preLink
和postLink
被执行)的内部函数。 在调用指令函数之前和之后,angular函数内会发生各种各样的事情。 也许最显着的是孩子recursion。 下面的简单说明显示了编译和链接阶段的关键步骤:
为了演示这些步骤,我们使用下面的HTML标记:
<div ng-repeat="i in [0,1,2]"> <my-element> <div>Inner content</div> </my-element> </div>
遵循以下指令:
myApp.directive( 'myElement', function() { return { restrict: 'EA', transclude: true, template: '<div>{{label}}<div ng-transclude></div></div>' } });
编
compile
API看起来像这样:
compile: function compile( tElement, tAttributes ) { ... }
通常这些参数前缀为t
来表示所提供的元素和属性是源模板的属性,而不是实例的属性。
在调用compile
transcluded内容(如果有的话)之前,将模板应用于标记。 因此,提供给compile
函数的元素将如下所示:
<my-element> <div> "{{label}}" <div ng-transclude></div> </div> </my-element>
请注意,此处不会重新插入横切的内容。
在对指令的.compile
进行调用之后,Angular将遍历所有的子元素,包括那些可能刚刚被指令引入的元素(例如模板元素)。
实例创build
在我们的例子中,将会创build上面三个源模板的实例(通过ng-repeat
)。 因此,以下序列将执行三次,每个实例一次。
调节器
controller
API涉及:
controller: function( $scope, $element, $attrs, $transclude ) { ... }
进入链接阶段后,通过$compile
返回的链接函数现在提供了一个范围。
首先,链接函数根据请求创build一个子范围( scope: true
)或一个独立范围( scope: {...}
)。
然后执行控制器,提供实例元素的作用域。
预链接
pre-link
API如下所示:
function preLink( scope, element, attributes, controller ) { ... }
调用指令的.preLink
和.preLink
函数之间几乎没有任何.preLink
。 Angular还提供了如何使用每个应用程序的build议。
在.preLink
调用之后,链接函数将遍历每个子元素 – 调用正确的链接函数并将其附加到当前作用域(作为子元素的父作用域)。
post链接
post-link
API与pre-link
function类似:
function postLink( scope, element, attributes, controller ) { ... }
也许值得注意的是,一旦一个指令的.postLink
函数被调用,它的所有子元素的链接过程就完成了,包括所有孩子的.postLink
函数。
这意味着,在.postLink
被调用的时候,孩子们已经准备好了。 这包括:
- 数据绑定
- 应用跨越式
- 附加范围
这个阶段的模板将如下所示:
<my-element> <div class="ng-binding"> "{{label}}" <div ng-transclude> <div class="ng-scope">Inner content</div> </div> </div> </my-element>
如何声明各种function?
编译,控制器,预链接和后链接
如果要使用全部四个函数,指令将遵循以下forms:
myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, compile: function compile( tElement, tAttributes, transcludeFn ) { // Compile code goes here. return { pre: function preLink( scope, element, attributes, controller, transcludeFn ) { // Pre-link code goes here }, post: function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here } }; } }; });
注意,编译返回一个包含前链接和后链接function的对象; 在Angular语言中,我们说编译函数返回一个模板函数 。
编译,控制器和后链接
如果不需要pre-link
,那么编译函数可以简单地返回链接后的函数,而不是定义对象,如下所示:
myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, compile: function compile( tElement, tAttributes, transcludeFn ) { // Compile code goes here. return function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here }; } }; });
有时候,希望在定义(post) link
方法之后添加一个compile
方法。 为此,可以使用:
myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, compile: function compile( tElement, tAttributes, transcludeFn ) { // Compile code goes here. return this.link; }, link: function( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here } }; });
控制器和后链接
如果不需要编译函数,那么可以完全跳过它的声明,并在指令configuration对象的link
属性下提供后链接function:
myApp.directive( 'myDirective', function () { return { restrict: 'EA', controller: function( $scope, $element, $attrs, $transclude ) { // Controller code goes here. }, link: function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here }, }; });
没有控制器
在上面的任何一个例子中,如果不需要,可以简单地删除controller
function。 例如,如果只需要post-link
function,可以使用:
myApp.directive( 'myDirective', function () { return { restrict: 'EA', link: function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here }, }; });
源模板和实例模板有什么区别?
Angular允许DOM操作的事实意味着编译过程中的input标记有时会与输出不同。 特别是,在渲染到DOM之前,某些input标记可能被克隆了几次(如使用ng-repeat
)。
angular度术语有点不一致,但它仍然区分两种types的标记:
- 源模板 – 需要克隆的标记。 如果克隆,这个标记将不会呈现给DOM。
- 实例模板 – 要呈现给DOM的实际标记。 如果涉及到克隆,每个实例将是一个克隆。
以下标记演示了这一点:
<div ng-repeat="i in [0,1,2]"> <my-directive>{{i}}</my-directive> </div>
源html定义
<my-directive>{{i}}</my-directive>
作为源模板。
但是,由于它被包含在一个ng-repeat
指令中,所以这个源模板将被克隆(在我们的例子中是3次)。 这些克隆是实例模板,每个都会出现在DOM中并绑定到相关的作用域。
编译function
当Angular引导时,每个指令的compile
函数只调用一次。
官方,这是执行(源)模板操作的地方,不涉及范围或数据绑定。
首先,这是为了优化目的而完成的; 考虑以下标记:
<tr ng-repeat="raw in raws"> <my-raw></my-raw> </tr>
<my-raw>
指令将呈现一组特定的DOM标记。 所以我们可以:
- 允许
ng-repeat
复制源模板(<my-raw>
),然后修改每个实例模板(在compile
函数外部)的标记。 - 修改源模板,使其包含所需的标记(在
compile
函数中),然后允许ng-repeat
将其复制。
如果raws
收集中有1000个项目,则后一个选项可能会比前一个更快。
做:
- 操作标记,使其充当实例(克隆)的模板。
不要
- 附加事件处理程序。
- 检查子元素。
- build立对属性的观察。
- 在示波器上设置手表。
后链接function
当post-link
function被调用时,所有先前的步骤都发生了 – 绑定,变换等
这通常是进一步操作呈现的DOM的地方。
做:
- 操纵DOM(呈现,因此实例化)元素。
- 附加事件处理程序。
- 检查子元素。
- build立对属性的观察。
- 在示波器上设置手表。
控制器function
每当一个新的相关元素被实例化时,每个指令的controller
函数都会被调用。
官方的controller
function是:
- 定义可以在控制器之间共享的控制器逻辑(方法)。
- 启动范围variables。
同样,重要的是要记住,如果指令涉及一个独立的作用域,那么它内部从父作用域inheritance的任何属性都不可用。
做:
- 定义控制器逻辑
- 启动范围variables
不要:
- 检查子元素(它们可能还没有渲染,绑定到范围等)。
预链接function
每当一个新的相关元素被实例化时,每个指令的pre-link
函数都会被调用。
正如前面在编译顺序部分所看到的, pre-link
函数被称为父 – 后 – 子,而后post-link
函数被称为child-then-parent
。
pre-link
function很less使用,但可以在特殊场景中使用; 例如,当一个子控制器向父控制器注册自己,但是注册必须是parent-then-child
时尚( ngModelController
这样做)。
不要:
- 检查子元素(它们可能还没有渲染,绑定到范围等)。