【问题标题】:Angular directives - when and how to use compile, controller, pre-link and post-link [closed]Angular 指令 - 何时以及如何使用编译、控制器、预链接和后链接 [关闭]
【发布时间】:2014-08-28 04:50:33
【问题描述】:

【问题讨论】:

  • 什么什么?
  • @Ian See:Operator overloading。本质上,这是为社区 wiki 设计的。太多相关问题的答案都是片面的,没有提供完整的情况。
  • 这是很棒的内容,但我们要求这里的所有内容都保持在问答格式中。也许您想将其分解为多个离散的问题,然后从标签 wiki 链接到它们?
  • 尽管这篇文章是题外话并且是博客形式的,但它在提供对 Angular 指令的深入解释方面最有用。请不要删除这篇文章,管理员!
  • 老实说,我什至不关心原始文档。 stackoverflow 帖子或博客通常能让我在几秒钟内完成,而我需要 15 到 30 分钟才能理解原始文档。

标签: angularjs angularjs-directive


【解决方案1】:

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

各种指令函数是从另外两个称为$compile(执行指令的compile)和一个称为nodeLinkFn(其中指令的controllerpreLink和@ 987654327@ 被执行)。在调用指令函数之前和之后,角度函数中会发生各种事情。也许最值得注意的是子递归。以下简化图显示了编译和链接阶段的关键步骤:

为了演示这些步骤,让我们使用以下 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 之前,已嵌入的内容(如果有)被移除,并且模板被应用于标记。因此,提供给compile 函数的元素将如下所示:

<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>

请注意,此时不会重新插入已嵌入的内容。

在调用指令的.compile 之后,Angular 将遍历所有子元素,包括那些可能刚刚被指令引入的子元素(例如模板元素)。

实例创建

在我们的例子中,将创建上述源模板的三个实例(由ng-repeat 创建)。因此,以下序列将执行 3 次,每个实例执行一次。

控制器

controller API 涉及:

controller: function( $scope, $element, $attrs, $transclude ) { ... }

进入链接阶段,通过$compile返回的链接函数现在提供了一个作用域。

首先,如果需要,链接函数会创建子作用域 (scope: true) 或隔离作用域 (scope: {...})。

然后执行控制器,并提供实例元素的范围。

预链接

pre-link API 如下所示:

function preLink( scope, element, attributes, controller ) { ... }

在调用指令的.controller.preLink 函数之间几乎没有发生任何事情。 Angular 仍然提供关于如何使用它们的建议。

.preLink 调用之后,链接函数将遍历每个子元素 - 调用正确的链接函数并将当前范围(用作子元素的父范围)附加到它。

后链接

post-link API 类似于pre-link 函数:

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>

【讨论】:

  • 你是如何创作这幅画的?
  • @RoyiNamir Omnigraffle。
【解决方案2】:

指令函数的执行顺序是什么?

对于单个指令

基于以下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

对于嵌套指令

注意:以下内容不适用于在其链接函数中呈现其子级的指令。很多 Angular 指令都是这样做的(比如 ngIf、ngRepeat 或任何带有transclude 的指令)。这些指令将在其子指令compile 被调用之前在本机将其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)

我们可以在这里区分两个阶段 - compile 阶段和 link 阶段。

编译阶段

当加载 DOM 时,Angular 开始编译阶段,它自上而下遍历标记,并在所有指令上调用 compile。从图形上看,我们可以这样表达:

也许需要提一下,在这个阶段,compile 函数获取的模板是源模板(而不是实例模板)。

链接阶段

DOM 实例通常只是将源模板渲染到 DOM 的结果,但它们可能由 ng-repeat 创建或动态引入。

每当带有指令的元素的新实例被渲染到 DOM 时,链接阶段就开始了。

在这个阶段,Angular 调用controllerpre-link,迭代子代,并在所有指令上调用post-link,如下所示:

【讨论】:

  • @lzhaki 流程图看起来不错。介意分享图表工具的名称吗? :)
  • @merlin 我用过 OmniGraffle(但也可以用 illustrator 或 inkscape - 除了速度,就这个插图而言,没有什么 OmniGraffle 比其他图表工具做得更好)。
  • @Anant 的 plunker 消失了,所以这是一个新的:plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview 打开 JS 控制台查看日志语句
  • 为什么当 ng-repeat 用于子指令时这不是真的???见plunk:plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview
  • @Luckylooke 您的 plunk 在 ng-repeat 下没有带有指令的子代(即,重复的是带有指令的模板。如果是这样,您会看到它们的编译仅在之后调用ng-repeat 的链接。
【解决方案3】:

控制器功能

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

正式地,controller 函数是一个:

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

同样,重要的是要记住,如果指令涉及隔离范围,则其中从父范围继承的任何属性尚不可用。

做:

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

不要:

  • 检查子元素(它们可能尚未渲染、绑定到范围等)。

【讨论】:

  • 很高兴您在指令中提到 Controller 是初始化作用域的好地方。我很难发现这一点。
  • 控制器不“启动作用域”,它只访问已经独立于它启动的作用域。
  • @DmitriZaitsev 非常注重细节。我已经修改了文本。
【解决方案4】:

预链接功能

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

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

pre-link 函数很少使用,但在特殊场景下可以派上用场;例如,当子控制器向父控制器注册自己,但注册必须采用parent-then-child 方式(ngModelController 这样做)。

不要:

  • 检查子元素(它们可能尚未渲染、绑定到范围等)。

【讨论】:

    【解决方案5】:

    编译函数

    每个指令的 compile 函数仅在 Angular 引导时调用一次。

    正式而言,这是执行不涉及范围或数据绑定的(源)模板操作的地方。

    这主要是为了优化目的;考虑以下标记:

    <tr ng-repeat="raw in raws">
        <my-raw></my-raw>
    </tr>
    

    &lt;my-raw&gt; 指令将呈现一组特定的 DOM 标记。所以我们可以:

    • 允许ng-repeat复制源模板(&lt;my-raw&gt;),然后修改每个实例模板的标记(在compile函数之外)。
    • 修改源模板以包含所需的标记(在compile 函数中),然后允许ng-repeat 复制它。

    如果raws 集合中有 1000 个项目,则后一种选项可能比前一种更快。

    做:

    • 处理标记,使其用作实例(克隆)的模板。

    不要

    • 附加事件处理程序。
    • 检查子元素。
    • 设置属性观察。
    • 在示波器上设置手表。

    【讨论】:

      【解决方案6】:

      后链接功能

      post-link函数被调用时,前面的所有步骤都已经发生了——绑定、嵌入等等。

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

      做:

      • 操作 DOM(渲染并因此实例化)元素。
      • 附加事件处理程序。
      • 检查子元素。
      • 设置属性观察。
      • 在示波器上设置手表。

      【讨论】:

      • 万一有人在使用链接功能(没有前置链接或后置链接),很高兴知道它相当于后置链接。
      【解决方案7】:

      如何声明各种函数?

      编译、控制器、预链接和后链接

      如果要使用所有四个功能,该指令将遵循以下形式:

      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 术语中,我们说编译函数返回一个 模板函数

      编译、控制器和后链接

      如果不需要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                 
                  };
              }
          };  
      });
      

      有时,希望在定义(发布)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
              }
      
          };  
      });
      

      控制器和后链接

      如果不需要编译功能,可以完全跳过其声明,并在指令配置对象的link属性下提供链接后功能:

      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 函数。例如,如果只需要post-link 函数,可以使用:

      myApp.directive( 'myDirective', function () {
          return {
              restrict: 'EA',
              link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                      // Post-link code goes here                 
              },          
          };  
      });
      

      【讨论】:

        【解决方案8】:

        源模板实例模板有什么区别?

        Angular 允许 DOM 操作这一事实意味着编译过程中的输入标记有时与输出不同。特别是,某些输入标记可能会在被渲染到 DOM 之前被克隆几次(例如使用 ng-repeat)。

        Angular 术语有点不一致,但它仍然区分了两种类型的标记:

        • 源模板 - 如果需要,要克隆的标记。如果克隆,此标记将不会呈现到 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 中并绑定到相关范围。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-06-21
          • 1970-01-01
          • 1970-01-01
          • 2012-09-14
          • 2015-09-15
          • 2013-03-18
          相关资源
          最近更新 更多