Angular directives - when and how to use compile, controller, pre-link and post-link
在编写Angular指令时,可以使用以下任何函数来操作声明指令的元素的DOM行为,内容和外观:
- 编
- 调节器
- 前链路
- 后链接
对于应该使用哪种功能似乎存在一些混淆。这个问题包括:
指令基础知识
- 如何申报各种功能?
- 源模板和实例模板有什么区别?
- 执行指令函数的顺序是什么?
- 这些函数调用之间还会发生什么?
功能性,做和不做
- 编
- 调节器
- 预链接
- 帖子链接
相关问题:
- 指令:链接vs编译与控制器。
- 定义angular.js指令时,'controller','link'和'compile'函数之间的区别。
- angularjs中的编译和链接函数有什么区别。
- AngularJS指令中的pre-compile和post-compile元素之间的区别?
- Angular JS指令 - 模板,编译或链接?
- 在Angular js指令中发布链接与预链接。
执行指令函数的顺序是什么?
对于单个指令
基于以下plunk,请考虑以下HTML标记:
1 2 3 | <body> </body> |
使用以下指令声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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)' ); } }; } }; }); |
控制台输出将是:
1 2 3 4 | some-div (compile) some-div (controller) some-div (pre-link) some-div (post-link) |
我们可以看到先执行
对于嵌套指令
Note: The following does not apply to directives that render their children in their link function. Quite a few Angular directives do so (like ngIf, ngRepeat, or any directive with
transclude ). These directives will natively have theirlink function called before their child directivescompile is called.
原始HTML标记通常由嵌套元素组成,每个元素都有自己的指令。如下面的标记(参见plunk):
1 2 3 4 5 6 | <body> </body> |
控制台输出如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 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开始编译阶段,它在顶部向下遍历标记,并在所有指令上调用
或许重要的是要提到,在这个阶段,编译函数获取的模板是源模板(而不是实例模板)。
链接阶段
DOM实例通常只是将源模板呈现给DOM的结果,但它们可以由
每当具有指令的元素的新实例被呈现给DOM时,链接阶段就开始了。
在这个阶段,Angular调用
这些函数调用之间还会发生什么?
各种指令函数在另外两个名为
为了演示这些步骤,我们使用以下HTML标记:
1 2 3 | <my-element> Inner content </my-element> |
使用以下指令:
1 2 3 4 5 6 7 | myApp.directive( 'myElement', function() { return { restrict: 'EA', transclude: true, template: '{{label}}' } }); |
编
1 | compile: function compile( tElement, tAttributes ) { ... } |
参数通常以
在调用
1 2 3 4 5 6 | <my-element> "{{label}}" </my-element> |
请注意,此时不会重新插入已转换的内容。
在调用指令的
实例创建
在我们的例子中,将创建上面的源模板的三个实例(通过
调节器
1 | controller: function( $scope, $element, $attrs, $transclude ) { ... } |
进入链接阶段,通过
首先,如果请求,链接函数创建子范围(
然后执行控制器,提供实例元素的范围。
预链接
1 | function preLink( scope, element, attributes, controller ) { ... } |
在对指令的
在
帖子链接
1 | function postLink( scope, element, attributes, controller ) { ... } |
也许值得注意的是,一旦调用了指令的
这意味着,当
- 数据绑定
- 应用包含
- 附加范围
因此,此阶段的模板将如下所示:
1 2 3 4 5 6 7 8 | <my-element> "{{label}}" Inner content </my-element> |
如何申报各种功能?
编译,控制器,预链接和后链接
如果要使用全部四个函数,该指令将遵循以下形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 } }; } }; }); |
请注意,compile返回一个包含pre-link和post-link函数的对象;在Angular lingo中我们说compile函数返回一个模板函数。
编译,控制器和后链接
如果不需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 } }; }); |
控制器和后链接
如果不需要编译函数,可以完全跳过它的声明,并在指令的配置对象的
1 2 3 4 5 6 7 8 9 10 11 | 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 }, }; }); |
没有控制器
在上面的任何示例中,如果不需要,可以简单地移除
1 2 3 4 5 6 7 8 | myApp.directive( 'myDirective', function () { return { restrict: 'EA', link: function postLink( scope, element, attributes, controller, transcludeFn ) { // Post-link code goes here }, }; }); |
源模板和实例模板有什么区别?
Angular允许DOM操作的事实意味着编译过程中的输入标记有时与输出不同。特别是,在渲染到DOM之前,可以将一些输入标记克隆几次(例如用
角度术语有点不一致,但它仍然区分两种类型的标记:
- 源模板 - 如果需要,可以克隆的标记。如果克隆,则此标记将不会呈现给DOM。
- 实例模板 - 要呈现给DOM的实际标记。如果涉及克隆,则每个实例都将是克隆。
以下标记演示了这一点:
1 | <my-directive>{{i}}</my-directive> |
源html定义
1 | <my-directive>{{i}}</my-directive> |
它充当源模板。
但由于它包含在
编译功能
当Angular bootstraps时,每个指令的
正式地说,这是执行(源)模板操作的地方,不涉及范围或数据绑定。
首先,这是为了优化目的;考虑以下标记:
1 2 3 | <tr ng-repeat="raw in raws"> <my-raw></my-raw> </tr> |
-
允许
ng-repeat 复制源模板( ),然后修改每个实例模板的标记(在compile 函数之外)。 -
修改源模板以包含所需的标记(在
compile 函数中),然后允许ng-repeat 复制它。
如果
做:
- 操纵标记,使其作为实例(克隆)的模板。
不要
- 附加事件处理程序。
- 检查子元素。
- 设置对属性的观察。
- 在示波器上设置手表。
控制器功能
每当实例化新的相关元素时,都会调用每个指令的
正式地说,
- 定义可在控制器之间共享的控制器逻辑(方法)。
- 启动范围变量。
同样,重要的是要记住,如果指令涉及隔离范围,则其中继承自父范围的任何属性都不可用。
做:
- 定义控制器逻辑
- 启动范围变量
不要:
- 检查子元素(它们可能尚未呈现,受范围限制等)。
后链接功能
当调用
这通常是进一步操纵渲染DOM的地方。
做:
- 操纵DOM(渲染,因此实例化)元素。
- 附加事件处理程序。
- 检查子元素。
- 设置对属性的观察。
- 在示波器上设置手表。
预链接功能
每当实例化新的相关元素时,都会调用每个指令的
如前面编译顺序部分所示,
不要:
- 检查子元素(它们可能尚未呈现,受范围限制等)。