AngularJS中的指令模板函数有哪些好处?

35
根据文档,template可以是一个函数,它接受两个参数,一个 elementattributes,并返回表示模板的字符串值。它用HTML内容替换当前元素。替换过程将所有属性和类从旧元素迁移到新元素中。 compile函数处理转换模板DOM。它接受三个参数,一个element, attributestransclude函数。已弃用transclude参数。它返回一个link函数。
似乎template函数和compile函数非常相似且可以实现相同的目标。template函数定义了一个模板,而compile函数修改了模板DOM。但是,这可以在template函数本身中完成。我看不出为什么要在template函数外部修改模板DOM。反之,如果DOM可以在compile函数中修改,那么需要template函数吗?

1
如果您返回一个依赖于模板函数属性的字符串,使用的代码比编译时少,因为不必注入$compile并使用它。Angular将在内部执行此操作。 - charlietfl
我不太明白。你能举个例子吗?你是在说返回一个使用attrs值的模板字符串吗?像这样 "<div size=\"" + attrs.size + "\"></div>" - Maksym Bykovskyy
是的,根据您的示例进行翻译。但也可能包含一些条件语句... if(attrs.type=='somVal') {return string1;}else{ return string2} - charlietfl
好的,我明白了,类似于<input type="text|button|checkbox|...">,其中属性不需要编译。 - Maksym Bykovskyy
如果属性不需要插值,我应该说真的。 - Maksym Bykovskyy
2个回答

50

编译函数可用于在将生成的模板函数绑定到作用域之前更改DOM。

请考虑以下示例:

<div my-directive></div>

您可以使用编译函数像这样更改模板DOM:

app.directive('myDirective', function(){
  return {

    // Compile function acts on template DOM
    // This happens before it is bound to the scope, so that is why no scope
    // is injected
    compile: function(tElem, tAttrs){

      // This will change the markup before it is passed to the link function
      // and the "another-directive" directive will also be processed by Angular
      tElem.append('<div another-directive></div>');

      // Link function acts on instance, not on template and is passed the scope
      // to generate a dynamic view
      return function(scope, iElem, iAttrs){

        // When trying to add the same markup here, Angular will no longer
        // process the "another-directive" directive since the compilation is
        // already done and we're merely linking with the scope here
        iElem.append('<div another-directive></div>');
      }
    }
  }
});

如果您的指令需要,那么可以使用compile函数将模板DOM更改为任何您想要的内容。

在大多数情况下,tElemiElem将是相同的DOM元素,但有时候它们可能不同,如果指令克隆模板以生成多个副本(参见ngRepeat)。

在幕后,Angular使用了一个双向渲染过程(编译+链接),来复制编译好的DOM片段的副本,以防止Angular为每个实例处理(解析指令)相同的DOM,从而获得更好的性能。

希望这可以帮到您!


评论区之后添加:

templatecompile函数的区别:

Template函数

{
    template: function(tElem, tAttrs){

        // Generate string content that will be used by the template
        // function to replace the innerHTML with or replace the
        // complete markup with in case of 'replace:true'
        return 'string to use as template';
    }
}

编译函数

{
    compile: function(tElem, tAttrs){

        // Manipulate DOM of the element yourself
        // and return linking function
        return linkFn(){};
    }
}

编译函数被调用之前会调用模板函数。

尽管它们可以执行几乎相同的操作并共享相同的“签名”,但关键区别在于模板函数的返回值将替换指令的内容(如果replace:true则替换整个指令标记),而编译函数预计以编程方式更改DOM并返回链接函数(或带有前置和后置链接函数的对象)。

从这个意义上讲,如果您只需要使用字符串值替换内容,那么可以认为模板函数是某种方便函数,无需使用编译函数。

希望这有所帮助!


4
感谢您尝试回答我的问题,但您的回答并不是我所问的。我的问题是关于template: function(element, attrs) { return "<div></div>"; }函数与编译函数相比的好处。请帮我翻译一下。 - Maksym Bykovskyy
2
我更新了答案。如果您需要更多信息,请随时让我知道。谢谢! - jvandemo
2
那么你认为应该使用模板或编译函数吗?因为同时使用两者似乎有点多余。 - Maksym Bykovskyy
3
是的,大多数情况下两者之一就足够了。哪个函数要取决于您是想返回一个字符串(模板函数)还是通过编程方式操作DOM(编译函数)。 - jvandemo
1
好的,我认为你的回答加上charlietfl的评论为我提供了一个很好的模板函数使用案例。谢谢! - Maksym Bykovskyy
另一个区别是编译函数中返回的链接函数不能包含隔离作用域。 - TheChrisONeil

7

模板函数最好的用途之一是有条件地生成模板。这样可以根据属性或任何其他条件自动创建模板。

我见过一些非常大的模板,使用ng-if来隐藏模板的某些部分。但是,你可以通过从模板函数的输出中删除永远不会使用的DOM部分,而不是将所有内容放入模板并使用ng-if,这可能会导致过多的绑定。

假设你有一个指令,将包含子指令item-firstitem-second。而且在外部指令的生命周期内,子指令永远不会改变。在编译函数被调用之前,你可以调整模板的输出。

<my-item data-type="first"></my-item>
<my-item data-type="second"></my-item>

这些的模板字符串将是:
<div>
  <item-first></item-first>
</div>

并且

<div>
  <item-second></item-second>
</div>

我同意这是一个极端简化的说法,但我有一些非常复杂的指令,而外部指令需要根据类型显示其中的一个约20个不同的内部指令。我可以在外部指令上设置类型,然后使用模板函数生成具有正确内部指令的正确模板,而不是使用transclude。然后将正确格式化的模板字符串传递给编译函数等等。

2
这是正确的答案,应该也被接受。作为证明,注意到Angular 1.5和2都强烈推荐使用组件而不是指令。组件将不允许您定义自定义compile()或link()函数,但会鼓励使用模板函数来处理像OP描述的情况。 - jose.angel.jimenez
如果您动态设置 data-type,例如 <my-item data-type="{{ $ctrl.someItem.dataType}}"></my-item>,会发生什么呢?看起来模板函数将会看到属性的字面值,例如 tAttr['data-type'] == '{{ $ctrl.someItem.dataType}}' 而不是 tAttr['data-type'] == 'first' :( - Dimitry K
我已经很久没用 AngularJS 了。但我似乎记得可以调用函数 $parse 来从 $ctrl.someItem.dataType 中获取值。至少我认为是 $parse - Intervalia
嗯...谢谢,这绝对是朝着正确方向迈出的一步。它允许将字符串表达式转换为函数,但该函数需要两个参数(context, locals),从中解析实际表达式。而在template()函数内,指令的$scope/context不可见... - Dimitry K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接