angularjs中的compile和link函数有什么区别


Answers:


217
  • 编译功能-用于模板 DOM操纵(即,tElement =模板元素的操纵),因此适用于与指令关联的模板的所有DOM克隆的操纵。

  • 链接功能-用于注册DOM侦听器(即实例范围内的$ watch表达式)以及实例 DOM操作(即iElement =单个实例元素的操作)。
    克隆模板后执行。例如,在<li ng-repeat ...>内部,已经为特定的<li>元素克隆了<li>模板(tElement)(将其插入到iElement中)之后,将执行链接功能。
    $ watch()允许指令通知实例范围属性更改(实例范围与每个实例相关联),这允许指令将更新后的实例值呈现给DOM-通过将内容从实例范围复制到DOM。

注意,可以在编译功能和/或链接功能中完成DOM转换。

大多数指令只需要一个链接函数,因为大多数指令只处理特定的DOM元素实例(及其实例范围)。

帮助确定使用哪个方法的一种方法:考虑编译函数未接收 scope参数。(我故意忽略了transclude链接函数参数,该参数接收到一个transcluded范围- 很少使用。)因此,compile函数无法执行需要(实例)范围的任何操作-您可以$不会监视任何模型/实例范围属性,不能使用实例范围信息来操作DOM,不能调用在实例范围内定义的函数,等等。

但是,编译功能(如链接功能)确实可以访问属性。因此,如果您的DOM操作不需要实例范围,则可以使用编译功能。这里有一个例子,只有使用编译功能,对于那些原因指令。它检查属性,但不需要实例范围即可完成其工作。

这是仅使用编译功能的指令示例。该指令仅需要转换模板DOM,因此可以使用编译功能。

帮助确定要使用哪种方法的另一种方法:如果您不使用链接功能中的“ element”参数,则可能不需要链接功能。

由于大多数指令都具有链接功能,因此我将不提供任何示例-应该很容易找到它们。

请注意,如果您需要编译函数和链接函数(或链接前和链接后的函数),则编译函数必须返回链接函数,因为如果定义了“编译”属性,则“链接”属性将被忽略。

也可以看看


5
关于编译与链接的最佳解释。
Nexus23

1
您说的if you don't use the "element" parameter in the link function, then you probably don't need a link function.是“范围”而不是“元素”吗?
杰森·拉克

69

我用头撞墙了几天,觉得还需要更多说明。

基本上,文档提到分离在很大程度上是性能的提高。我要重申的是,编译阶段主要用于需要在子元素本身被编译之前修改DOM的情况。

为了我们的目的,我要强调术语,否则会造成混淆:

编译器SERVICE($ compile)是处理DOM并运行指令中各种代码位的角度机制。

编译功能是指令中的一小段代码,指令在特定时间由编译器SERVICE($ compile)运行。

有关编译功能的一些注意事项:

  1. 您不能修改ROOT元素(您的指令所影响的那个),因为它已经从DOM的外部层进行了编译(编译SERVICE已经在该元素上扫描了指令)。

  2. 如果要将其他指令添加到(嵌套的)元素,则可以:

    1. 必须在编译阶段添加它们。

    2. 必须将编译服务注入链接阶段并手动编译元素。但是,提防编译两次!

查看嵌套和显式调用$ compile的工作方式也很有帮助,因此我在http://jsbin.com/imUPAMoV/1/edit创建了一个游乐场,供您查看。基本上,它只是将步骤记录到console.log。

我将在此处说明您在该bin中看到的结果。对于嵌套自定义指令tp和sp的DOM,如下所示:

<tp>
   <sp>
   </sp>
</tp>

角编译SERVICE将调用:

tp compile
sp compile
tp pre-link
sp pre-link
sp post-link
tp post-link

jsbin代码还具有tp后链接功能,在第三个指令(向上)上显式调用编译服务,该指令最后执行所有三个步骤。

现在,我想通过几个场景来展示如何使用编译和链接来执行各种操作:

场景1:宏指令

您想将指令(例如ng-show)动态添加到可以从属性派生的模板中的某些内容。

假设您有一个templateUrl指向:

<div><span><input type="text"></span><div>

并且您想要一个自定义指令:

<my-field model="state" name="address"></my-field>

将DOM变成这样:

<div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>

基本上,您希望通过具有指令可以解释的一致模型结构来减少样板。换句话说:您想要一个宏。

这对于编译阶段很有用,因为您可以将所有DOM操作基于仅从属性中了解的内容。只需使用jQuery添加属性:

compile: function(tele, tattr) {
   var span = jQuery(tele).find('span').first();
   span.attr('ng-show', tattr.model + ".visible." + tattr.name);
   ...
   return { 
     pre: function() { },
     post: function() {}
   };
}

操作顺序为(您可以通过前面提到的jsbin看到它):

  1. 编译SERVICE找到我的领域
  2. 它在指令上调用编译FUNCTION,从而更新DOM。
  3. 然后,编译SERVICE进入生成的DOM和COMPILES(递归)
  4. 然后,编译服务会自顶向下调用预链接
  5. 然后,编译SERVICE会调用后链接BOTTOM UP,因此在链接内部节点之后,称为my-field的链接功能。

在上面的示例中,不需要链接,因为所有指令的工作都是在编译FUNCTION中完成的。

在任何时候,指令中的代码都可以要求编译器SERVICE在其他元素上运行。

这意味着如果您注入编译服务,我们可以在链接函数中做完全相同的事情:

directive('d', function($compile) {
  return {
    // REMEMBER, link is called AFTER nested elements have been compiled and linked!
    link: function(scope, iele, iattr) {
      var span = jQuery(iele).find('span').first();
      span.attr('ng-show', iattr.model + ".visible." + iattr.name);
      // CAREFUL! If span had directives on it before
      // you will cause them to be processed again:
      $compile(span)(scope);
    }
});

如果您确定传递给$ compile SERVICE的元素最初是无指令的(例如,它们来自您定义的模板,或者只是使用angular.element()创建了它们),那么最终结果将是与以前相同(尽管您可能会重复一些工作)。但是,如果元素上还有其他指令,则只是使它们再次被处理,这可能导致各种不稳定的行为(例如,事件和手表的双重注册)。

因此,对于宏样式工作,编译阶段是一个更好的选择。

场景2:通过范围数据配置DOM

这是从上面的示例得出的。假设您在操作DOM时需要访问作用域。好吧,在这种情况下,编译部分对您毫无用处,因为它发生在作用域可用之前。

因此,假设您要使用验证来提供输入,但是要从服务器端ORM类(DRY)导出验证,并让它们自动应用并为这些验证生成正确的客户端UI。

您的模型可能会推送:

scope.metadata = {
  validations: {
     address: [ {
       pattern: '^[0-9]',
       message: "Address must begin with a number"
     },
     { maxlength: 100,
       message: "Address too long"
     } ]
  }
};
scope.state = {
  address: '123 Fern Dr'
};

并且您可能想要一个指令:

<form name="theForm">
  <my-field model="state" metadata="metadata" name="address">
</form>

自动包含适当的指令和div以显示各种验证错误:

<form name="theForm">
  <div>
    <input ng-model="state.address" type="text">
    <div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
...

在这种情况下,您肯定需要访问范围(因为验证存储在该范围内),并且将不得不手动编译添加项,再次注意不要对内容进行双重编译。(作为旁注,您需要在包含的表单标签上设置一个名称(我在这里假设是TheForm),并且可以通过与iElement.parent()。controller('form')。$ name的链接来访问它) 。

在这种情况下,编写编译函数毫无意义。链接确实是您想要的。步骤将是:

  1. 定义一个完全没有角度指令的模板。
  2. 定义添加各种属性的链接函数
  3. 删除可能在顶级元素上允许的所有角度指令(my-field指令)。它们已被处理,这是防止它们被双重处理的一种方法。
  4. 通过在顶层元素上调用编译SERVICE来完成

像这样:

angular.module('app', []).
directive('my-field', function($compile) {
  return {
    link: function(scope, iele, iattr) {
      // jquery additions via attr()
      // remove ng attr from top-level iele (to avoid duplicate processing)
      $compile(iele)(scope); // will pick up additions
    }
  };
});

当然,您可以一个一个地编译嵌套的元素,以避免在再次编译顶级元素时不必担心ng指令的重复处理。

关于这种情况的最后一点说明:我暗示您将在服务器中推送验证的定义,并且在我的示例中,我已将它们显示为作用域中的数据。我将其作为一种练习,供读者弄清楚如何处理需要从REST API中提取数据的问题(提示:延迟编译)。

场景3:通过链接进行双向数据绑定

当然,链接的最常见用法是通过watch / apply挂接双向数据绑定。大多数指令都属于此类,因此在其他地方也有足够的介绍。


2
令人毛骨悚然的答案!
Nexus23 2014年

如何在不进行双重编译的情况下添加嵌套元素?
Art713

50

从文档:

编译器

编译器是一种有角度的服务,它遍历DOM寻找属性。编译过程分为两个阶段。

  1. 编译:遍历DOM并收集所有指令。结果是链接功能。

  2. 链接:将指令与作用域结合在一起并产生实时视图。范围模型中的任何更改都将反映在视图中,并且用户与视图的任何交互都将反映在范围模型中。使作用域模型成为事实的唯一来源。

有些指令(例如)ng-repeat会对集合中的每个项目一次克隆DOM元素。编译和链接阶段可以提高性能,因为克隆的模板只需要编译一次,然后为每个克隆实例链接一次。

因此,至少在某些情况下,这两个阶段是作为优化单独存在的。


来自@UmurKontacı

如果要进行DOM转换,则应为compile。如果要添加一些行为更改的功能,则应在中link


46
如果要进行DOM转换,则应该compile添加一些功能,例如行为更改,应该在中link
UmurKontacı2012年

4
+1以上评论;这是我到目前为止找到的最简洁的描述。它与我在这里找到的教程相匹配。
Benny Bottema

18

这是Misko关于指令的演讲。http://youtu.be/WqmeI5fZcho?t=16m23s

可以将编译器功能视为可在模板上运行的事物,并可以通过例如向其添加类或类似的东西来更改模板本身。但是实际上是链接功能将两者绑定在一起,因为链接功能可以访问作用域,并且链接功能针对特定模板的每个实例执行一次。因此,可以放在编译函数中的唯一事物是所有实例之间共有的事物。


10

线程有点晚了。但是,为了将来的读者的利益:

我看了以下视频,以非常棒的方式解释了Angular JS中的编译和链接:

https://www.youtube.com/watch?v=bjFqSyddCeA

在这里复制/键入所有内容将不令人满意。我从视频中截取了几个屏幕快照,这些屏幕快照解释了编译和链接阶段的每个阶段:

Angular JS中的编译和链接

Angular JS中的编译和链接-嵌套指令

第二个屏幕截图有点令人困惑。但是,如果我们遵循步骤编号,那就很简单了。

第一个周期:首先对所有指令执行“编译”。
第二个周期:“控制器”和“前链接”被执行(一个接一个)第三个周期:“后链接”以相反的顺序被执行(从最里面开始)

以下是代码,演示了上述内容:

var app = angular.module('app',[]);

app.controller('msg',['$ scope',function($ scope){

}]);

app.directive('message',function($ interpolate){
    返回{

        编译:function(tElement,tAttributes){ 
            console.log(tAttributes.text +“-正在编译..”);
            返回{

                上一个:函数(作用域,iElement,iAttributes,控制器){
                    console.log(iAttributes.text +“ -In pre ..”);
                },

                帖子:函数(作用域,iElement,iAttributes,控制器){
                    console.log(iAttributes.text +“ -In Post ..”);
                }

            }
        },

        控制器:功能($ scope,$ element,$ attrs){
            console.log($ attrs.text +“ -In控制器..”);
        },

    }
});
<body ng-app="app">
<div ng-controller="msg">
    <div message text="first">
        <div message text="..second">
            <div message text="....third">

            </div>              
        </div>  
    </div>
</div>

更新:

同一视频的第2部分可在以下位置找到:https : //www.youtube.com/watch?v=1M3LZ1cu7rw 该视频以一个简单的示例详细说明了如何在Angular JS的编译和链接过程中修改DOM和处理事件。 。


在从供应商指令部分修改DOM之前,使用compilepost修改DOM template
jedi

6

两个阶段:编译和链接

编译:

遍历DOM树以查找指令(元素/属性/类/注释)。指令的每次编译都可以修改其模板,或修改其尚未编译的内容。指令匹配后,它会返回一个链接函数,该链接函数在以后的阶段中用于将元素链接在一起。在编译阶段的最后,我们有一个已编译指令及其对应链接功能的列表。

链接:

链接元素时,DOM树在DOM树中的分支点断开,其内容由模板的已编译(和链接)实例替换。原始替换的内容将被丢弃,或者在包含的情况下,重新链接回模板。通过包含,两个部分链接回在一起(有点像一条链,模板部分在中间)。调用链接函数时,模板已绑定到作用域,并已添加为元素的子级。链接功能是您进一步操纵DOM并设置更改侦听器的机会。


3

我想简短总结一下这个问题,可能会有所帮助:

  • 为所有指令实例调用一次编译
  • 编译的主要目的是返回/创建链接(可能还有前/后)函数/对象。您还可以初始化在指令实例之间共享的东西。
  • 我认为,“链接”是此功能的一个令人困惑的名称。我希望使用“预渲染”。
  • 每个指令实例都会调用link,其目的是准备在DOM中呈现指令。

1
建议名称加号:“预渲染”
曹海龙
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.