有人能简单解释一下吗?

文档看起来有点迟钝。我没有领会到什么时候该用一种而不是另一种的本质和大局。一个对比这两者的例子会很棒。


当前回答

compile function - use for template DOM manipulation (i.e., manipulation of tElement = template element), hence manipulations that apply to all DOM clones of the template associated with the directive. link function - use for registering DOM listeners (i.e., $watch expressions on the instance scope) as well as instance DOM manipulation (i.e., manipulation of iElement = individual instance element). It is executed after the template has been cloned. E.g., inside an <li ng-repeat...>, the link function is executed after the <li> template (tElement) has been cloned (into an iElement) for that particular <li> element. A $watch() allows a directive to be notified of instance scope property changes (an instance scope is associated with each instance), which allows the directive to render an updated instance value to the DOM -- by copying content from the instance scope into the DOM.

注意,DOM转换可以在compile函数和/或link函数中完成。

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

One way to help determine which to use: consider that the compile function does not receive a scope argument. (I'm purposely ignoring the transclude linking function argument, which receives a transcluded scope -- this is rarely used.) So the compile function can't do anything you would want to do that requires an (instance) scope -- you can't $watch any model/instance scope properties, you can't manipulate the DOM using instance scope information, you can't call functions defined on the instance scope, etc.

但是,compile函数(像link函数一样)可以访问属性。因此,如果DOM操作不需要实例作用域,则可以使用compile函数。由于这些原因,这里有一个只使用compile函数的指令示例。它检查属性,但是不需要实例作用域来完成它的工作。

下面是一个同样只使用compile函数的指令示例。该指令只需要转换模板DOM,因此可以使用compile函数。

另一种帮助确定使用哪个的方法:如果在链接函数中不使用"element"参数,那么可能不需要链接函数。

因为大多数指令都有一个链接函数,所以我不打算提供任何示例——它们应该很容易找到。

注意,如果你需要一个编译函数和一个链接函数(或前和后链接函数),编译函数必须返回一个或多个链接函数,因为如果定义了'compile'属性,'link'属性将被忽略。

另请参阅

在定义指令时,'controller', 'link'和'compile'函数之间的区别 Dave Smith关于指令的精彩ng-conf 2104演讲(链接到视频中关于编译和链接的部分)

其他回答

从文档中可以看出:

Compiler Compiler is an angular service which traverses the DOM looking for attributes. The compilation process happens into two phases. Compile: traverse the DOM and collect all of the directives. The result is a linking function. Link: combine the directives with a scope and produce a live view. Any changes in the scope model are reflected in the view, and any user interactions with the view are reflected in the scope model. Making the scope model a single source of truth. Some directives such ng-repeat clone DOM elements once for each item in collection. Having a compile and link phase improves performance since the cloned template only needs to be compiled once, and then linked once for each clone instance.

所以至少在某些情况下,这两个阶段作为优化是分开存在的。


从@UmurKontacı:

如果要进行DOM转换,则应该进行compile。如果你想添加一些行为变化的特性,它应该在link中。

compile function - use for template DOM manipulation (i.e., manipulation of tElement = template element), hence manipulations that apply to all DOM clones of the template associated with the directive. link function - use for registering DOM listeners (i.e., $watch expressions on the instance scope) as well as instance DOM manipulation (i.e., manipulation of iElement = individual instance element). It is executed after the template has been cloned. E.g., inside an <li ng-repeat...>, the link function is executed after the <li> template (tElement) has been cloned (into an iElement) for that particular <li> element. A $watch() allows a directive to be notified of instance scope property changes (an instance scope is associated with each instance), which allows the directive to render an updated instance value to the DOM -- by copying content from the instance scope into the DOM.

注意,DOM转换可以在compile函数和/或link函数中完成。

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

One way to help determine which to use: consider that the compile function does not receive a scope argument. (I'm purposely ignoring the transclude linking function argument, which receives a transcluded scope -- this is rarely used.) So the compile function can't do anything you would want to do that requires an (instance) scope -- you can't $watch any model/instance scope properties, you can't manipulate the DOM using instance scope information, you can't call functions defined on the instance scope, etc.

但是,compile函数(像link函数一样)可以访问属性。因此,如果DOM操作不需要实例作用域,则可以使用compile函数。由于这些原因,这里有一个只使用compile函数的指令示例。它检查属性,但是不需要实例作用域来完成它的工作。

下面是一个同样只使用compile函数的指令示例。该指令只需要转换模板DOM,因此可以使用compile函数。

另一种帮助确定使用哪个的方法:如果在链接函数中不使用"element"参数,那么可能不需要链接函数。

因为大多数指令都有一个链接函数,所以我不打算提供任何示例——它们应该很容易找到。

注意,如果你需要一个编译函数和一个链接函数(或前和后链接函数),编译函数必须返回一个或多个链接函数,因为如果定义了'compile'属性,'link'属性将被忽略。

另请参阅

在定义指令时,'controller', 'link'和'compile'函数之间的区别 Dave Smith关于指令的精彩ng-conf 2104演讲(链接到视频中关于编译和链接的部分)

两个阶段:编译和链接

编译:

遍历DOM树寻找指令(元素/属性/类/注释)。指令的每次编译都可以修改它的模板,或者修改它还没有编译的内容。一旦一个指令被匹配,它就返回一个链接函数,这个函数在后面的阶段用于将元素链接在一起。在编译阶段的最后,我们有一个已编译指令及其对应的链接函数列表。

链接:

When an element is linked, the DOM tree is broken at its branch point in the DOM tree, and the contents are replaced by the compiled (and linked) instance of the template. The original displaced content is either discarded, or in the case of transclusion, re-linked back into the template. With transclusion, the two pieces are linked back together (kind of like a chain, with the template piece being in the middle). When the link function is called, the template has already been bound to a scope, and added as a child of the element. The link function is your opportunity to manipulate the DOM further and setup change listeners.

我在这个问题上绞尽脑汁了好几天,我觉得应该再多解释一下。

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

为了我们的目的,我将强调术语,否则会令人困惑:

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

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

关于compile FUNCTION的一些注意事项:

您不能修改ROOT元素(指令影响的元素),因为它已经从DOM的外部层编译(compile SERVICE已经扫描了该元素上的指令)。 如果你想在(嵌套的)元素中添加其他指令,你可以: 必须在编译阶段添加它们。 必须将编译服务注入到链接阶段并手动编译元素。但是,要小心编译两次!

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

我将陈述你在这个箱子里看到的结果。对于自定义指令tp和sp的DOM,嵌套如下:

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

Angular的compile SERVICE会调用:

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

jsbin代码还有tp post-link FUNCTION在第三个指令(up)上显式调用compile SERVICE,该指令在最后完成所有三个步骤。

现在,我想通过几个场景来展示如何使用compile和link来做各种事情:

场景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看到):

compile SERVICE找到my-field 它调用指令上的compile FUNCTION来更新DOM。 然后,编译服务进入结果DOM,然后编译(递归地) 然后,compile SERVICE调用预链接自顶向下 然后compile SERVICE调用post-link BOTTOM - UP,因此my-field的link函数在内部节点链接之后调用。

在上面的例子中,不需要链接,因为所有指令的工作都是在compile 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>
...

在这种情况下,您肯定需要访问作用域(因为这是存储验证的地方),并且必须手动编译添加的内容,再次注意不要重复编译。(作为旁注,你需要在包含表单的标签上设置一个名称(我假设这里是form),并且可以通过element .parent().controller('form').$name链接访问它)。

在这种情况下,没有必要编写编译函数。链接是你真正想要的。步骤如下:

定义一个完全没有angular指令的模板。 定义一个添加各种属性的链接函数 移除所有允许在顶层元素上使用的angular指令(my-field指令)。它们已经被处理过了,这是一种防止它们被重复处理的方法。 通过在顶级元素上调用compile 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:通过链接进行双向数据绑定

当然,link最常见的用法是通过watch/apply简单地连接双向数据绑定。大多数指令都属于这一类,所以在其他地方有充分的介绍。

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

Think of the compiler function as the thing that works on a template and the thing that is allowed to change the template itself by, for example, adding a class to it or anything like that. But it's the linking function that actually does the work of binding the two together because the linking function has access to the scope and it's the linking function that executes once for each instantiation of the particular template. So the only kind of things you can placed inside of the compile functions are things that are common across all of the instances.