将指令替换为另一个指令而不编译子节点两次

时间:2016-02-26 16:23:46

标签: angularjs

在一个项目中,我构建了一个指令 aDir ,在其post-link函数中使用$ compile替换为另一个 bDir 。它为 bDir 创建了一个“快捷方式”,这很有用,因为 bDir 有很多参数,我在整个项目中使用它。

在我的模板中:

<p>
    <button a-dir></button>
</p>

编译在:

<p>
    <button b-dir arg1="" arg2="" ... ></button>
</p>

使用这样的代码非常有用:

function aDir($compile){
  return {
    restrict: 'A',
    link: function(scope, iElem, iAttrs){
      iElem.attr('b-dir', '');
      iElem.attr('arg1', '');
      iElem.attr('arg2', '');
      [...]
      iElem.removeAttr('a-dir'); // To avoid infinite loop
      $compile(iElem)(scope);
    }
  }
}

然而,如果应用 aDir 的元素具有子元素,则它们将被编译两次。一旦由Angular发起的$compile函数和我在{em> aDir 后链接中调用的$compile函数执行一次。

考虑this plunker。这是HTML:

<outer-level>
  <p replace-by-another>
    <inner-level>Hello World!</inner-level>
  </p>
</outer-level>

replaceByAnother 被名为 another 的指令替换。 outerLevel innerLevel 是指令,只有在调用其编译,前后链接函数时才会登录控制台。

控制台日志是:

outerLevel: compile
replaceByAnother: compile
innerLevel: compile
outerLevel: pre link
replaceByAnother: pre link
innerLevel: pre link
innerLevel: post link
replaceByAnother: post link
    another: compile
    innerLevel: compile
    another: pre link
    innerLevel: pre link
    innerLevel: post link
    another: post link
outerLevel: post link

因此我们有两个 innerLevel 的编译,预链接和后连接函数调用。在我的情况下,这完全可以,但我有兴趣完全理解$compile做了什么以及是否可以避免这种行为。

我通过在 replaceByAnother 指令中定义编译函数尝试了一些事情,但我只是设法改变执行顺序而不是只编译一次 innerLevel 指令:

http://plnkr.co/edit/ZnBRaskb1WPkRZv36giS

function replaceByAnother($compile){
  return {
    restrict: 'A',
    compile: function(tElem, tAttrs){
      console.log('replaceByAnother: compile');
      tElem.attr('another', '');
      tElem.removeAttr('replace-by-another');
      var anotherLinkFunc = $compile(tElem);

      return {
        pre: function(scope, iElem, iAttrs){
          console.log('replaceByAnother: pre link');
        },
        post: function(scope, iElem, iAttrs){
          console.log('replaceByAnother: post link');
          anotherLinkFunc(scope);
        }
      }
    }
  }
}

结果:

outerLevel: compile
replaceByAnother: compile
  another: compile
  innerLevel: compile
innerLevel: compile
outerLevel: pre link
replaceByAnother: pre link
innerLevel: pre link
innerLevel: post link
replaceByAnother: post link
  another: pre link
  innerLevel: pre link
  innerLevel: post link
  another: post link
outerLevel: post link

你有什么想法吗?

通过@georgeawg和@AndrésEsguerra的答案,我找到了一个令人满意的解决方案:

  • 使用terminal: true和高优先级来阻止Angular编译两个子节点。
  • 修改编译功能中的模板元素并在其上调用$compile。存储$compile
  • 的输出
  • 在link函数中调用此输出以将模板元素绑定到范围。
function replaceByAnother($compile){
  return {
    restrict: 'A',
    terminal: true,
    priority: 100000,
    compile: function(tElem, tAttrs){
      tElem.attr('another', '');
      tElem.removeAttr('replace-by-another');
      var anotherLinkFunc = $compile(tElem);

      return {
        pre: function(scope, iElem, iAttrs){
          // pre link
        },
        post: function(scope, iElem, iAttrs){
          anotherLinkFunc(scope, function cloneAttachedFn(clone) {
              iElem.replaceWith(clone);
          });
        }
      }
    }
  }
}

2 个答案:

答案 0 :(得分:4)

使用terminal: true和高优先级。 AngularJS按最高优先级编译指令,因此任何具有更高优先级的指令都将在您之前编译

文档:$compile

function replaceByAnother($compile){
return {
    restrict: 'A',
    terminal: true,
    priority: 1000000,
    compile: function(tElem, tAttrs){
      console.log('replaceByAnother: compile');
      tElem.attr('another', '');
      tElem.removeAttr('replace-by-another');
      var anotherLinkFunc = $compile(tElem);

      return {
        pre: function(scope, iElem, iAttrs){
          console.log('replaceByAnother: pre link');
        },
        post: function(scope, iElem, iAttrs){
          console.log('replaceByAnother: post link');
          anotherLinkFunc(scope);
        }
      }
    }
  }
}

答案 1 :(得分:1)

您可以通过克隆tElem并在编译阶段清空它来避免第一次内部编译。

//replaceByAnother Directive
function replaceByAnother($compile){
  return {
    restrict: 'A',
    compile: function(tElem, tAttrs){
      console.log('replaceByAnother: compile');
      //clone tElem
      var tClone = tElem.clone();
      //empty tElem
      tElem.empty();
      return {
        pre: function(scope, iElem, iAttrs){
          //console.log('replaceByAnother: pre link');
        },
        post: function(scope, iElem, iAttrs){
          //console.log('replaceByAnother: post link');
          //modify and compile cloned elements
          tClone.attr('another', '');
          tClone.removeAttr('replace-by-another');
          var linkFn = $compile(tClone);
          linkFn(scope, function transclude(clone) {
              iElem.append(clone);
          });
        }
      }
    }
  }
}

然后在postLink阶段修改和编译克隆元素。

DEMO on PLNKR