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呼叫controllerpre-link ,迭代子节点,并在所有的指令中调用post-link ,如下所示:

演示链接阶段步骤的插图

这些函数调用之间还会发生什么?

各种指令函数是在两个其他angular度函数中执行的,这两个函数称为$compile (其中指令的compile被执行)和一个名为nodeLinkFn (指令的controllerpreLinkpostLink被执行)的内部函数。 在调用指令函数之前和之后,angular函数内会发生各种各样的事情。 也许最显着的是孩子recursion。 下面的简单说明显示了编译和链接阶段的关键步骤:

显示Angular编译和链接阶段的插图

为了演示这些步骤,我们使用下面的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-linkfunction类似:

 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 }, }; }); 

没有控制器

在上面的任何一个例子中,如果不需要,可以简单地删除controllerfunction。 例如,如果只需要post-linkfunction,可以使用:

 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-linkfunction被调用时,所有先前的步骤都发生了 – 绑定,变换等

这通常是进一步操作呈现的DOM的地方。

做:

  • 操纵DOM(呈现,因此实例化)元素。
  • 附加事件处理程序。
  • 检查子元素。
  • build立对属性的观察。
  • 在示波器上设置手表。

控制器function

每当一个新的相关元素被实例化时,每个指令的controller函数都会被调用。

官方的controllerfunction是:

  • 定义可以在控制器之间共享的控制器逻辑(方法)。
  • 启动范围variables。

同样,重要的是要记住,如果指令涉及一个独立的作用域,那么它内部从父作用域inheritance的任何属性都不可用。

做:

  • 定义控制器逻辑
  • 启动范围variables

不要:

  • 检查子元素(它们可能还没有渲染,绑定到范围等)。

预链接function

每当一个新的相关元素被实例化时,每个指令的pre-link函数都会被调用。

正如前面在编译顺序部分所看到的, pre-link函数被称为父 – 后 – 子,而后post-link函数被称为child-then-parent

pre-linkfunction很less使用,但可以在特殊场景中使用; 例如,当一个子控制器向父控制器注册自己,但是注册必须是parent-then-child时尚( ngModelController这样做)。

不要:

  • 检查子元素(它们可能还没有渲染,绑定到范围等)。