我有一个类似列表的容器指令,该指令将内容转换为ng-repeat。

模板如下所示:

<div ng-repeat='item in items'>
    <div ng-transclude></div>
</div>

用法看起来像这样
<my-container>
    foo
</my-container>

这按预期工作。但是,只要模板中的ng-repeat上面有任何指令,例如
<div ng-style='{}'>
    <div ng-repeat='item in items'>
        <div ng-transclude></div>
    </div>
</div>

我的代码抛出
"Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <div ng-transclude="">"

首先看一下Angular代码,看来Angular没有将$ transclude注入(inject)正确地设置到ngTransclude Controller 中。我将开始研究Angular代码以尝试找出原因,但是如果有人已经知道发生了什么和/或如何解决或解决它,我将非常感激。

这是积极案例的功能齐全的 fiddle :http://jsfiddle.net/7BuNj/1/

这是一个否定情况的完全无效的 fiddle :http://jsfiddle.net/8BLYG/

最佳答案

tl;博士tl;博士tl;博士

看起来Angular递归编译周期不会通过带有链接函数的指令来向下线程化包含函数。我的ngStyle指令具有链接功能,因此在ngRepeat编译其模板时,包含功能已丢失。目前尚不清楚这是预期的行为还是错误;稍后,我将与Angular团队进行跟进。现在,我暂时用猴子修补了Angular v.1.2.0的副本,以将5593-5594行替换为

: compileNodes(childNodes,
  (nodeLinkFn && nodeLinkFn.transclude) ? nodeLinkFn.transclude : transcludeFn);

而且一切正常。

研究

好的。对于非Angular专家(例如,我:)来说,这毫无意义,我将对Angular的编译/链接周期的代码有所了解。我在这里提供了来自v1.2.0的相关内容的精简版供引用(对不起,Angular团队,我打破了我认为是相当虔诚的风格指南,试图使代码尽可能简短;) :
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) {
  ...
  for (var i = 0; i < nodeList.length; i++) {
    ...
    nodeLinkFn = (directives.length)
                 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext)
                 : null;
    ...
    childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !(childNodes = nodeList[i].childNodes) || !childNodes.length)
                  ? null
                  : compileNodes(childNodes, nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
    ...
    linkFns.push(nodeLinkFn, childLinkFn);
    ...
  }
  return linkFnFound ? compositeLinkFn : null;

  function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
    ...
    for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
      ...
      nodeLinkFn = linkFns[i++];
      childLinkFn = linkFns[i++];
      if (nodeLinkFn) {
        ...
        childTranscludeFn = nodeLinkFn.transclude;
        if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
          nodeLinkFn(childLinkFn, childScope, node, $rootElement, createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn));
        } else {
          nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
        }
      } else if (childLinkFn) {
        childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
      }
    }
  }
}

function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) {
  ...
  if (directiveValue = directive.transclude) {
    hasTranscludeDirective = true;
    if (directiveValue == 'element') {
      ...
      $template = groupScan(compileNode, attrStart, attrEnd);
      ...
      childTranscludeFn = compile($template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { ... });
    } else {
      $template = jqLite(jqLiteClone(compileNode)).contents();
      $compileNode.empty(); // clear contents
      childTranscludeFn = compile($template, transcludeFn);
    }
  }
  ...
  // setup preLinkFns and postLinkFns
  ...
  nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
  return nodeLinkFn;

  function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
    ...
    forEach(controllerDirectives, function(directive) {
      ...
      transcludeFn = boundTranscludeFn && controllersBoundTransclude;
      // puts transcludeFn into controller locals for $transclude access
      ...
    }
    ...
    // PRELINKING: call all preLinkFns with boundTranscludeFn
    ...
    // RECURSION
    ...
    childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
    ...
    // POSTLINKING: call all preLinkFns with boundTranscludeFn
    ...
  }
}

编译/链接无包含

本质上,编译周期通过compileNodes对DOM进行深度优先搜索。在每个节点上
  • 它将通过applyDirectivesToNode将该节点上的所有指令编译为一个nodeLinkFn,该节点可以访问任何已配置的预链接或后链接功能,并接受子CompositeLinkFn进行递归
  • 以递归方式将节点的子树编译为CompositeLinkFn
  • 将带有子树CompositeLinkFn的nodeLinkFn打包到节点级别的CompositeLinkFn中(如果为空则跳过nodeLinkFn)

  • 编译完成后,您将拥有一个顶级CompositeLinkFn,其执行深度优先地与编译过程完全并行地递归,并且在每个节点处执行所有预链接功能,然后递归然后执行所有后链接功能。

    编译/链接具有1级包含

    每当applyDirectivesToNode命中带有设置了transclude标志的指令时,它会将指令元素的内容完全编译到与当前编译递归分开的“transclude链接函数”中,并用其注释指令的nodeLinkFn。当最终执行封闭的CompositeLinkFn时,将读取此批注并将其作为boundTranscludeFn传递到nodeLinkFn中,在此最终将为 Controller $ transclude注入(inject)设置该批注,将其传递给所有prelink和poSTLink函数,并传递给子链接的递归调用功能。此boundTranscludeFn的递归线程是关键,因此您可以在包含指令的模板内的任何位置访问$ transclude。

    编译/链接具有多个级别的包含

    现在,如果我们在包含指令A的内部包含了包含指令B的内容,该怎么办?我们要发生的事情(我假设)是使指令A的transclude函数以某种方式可用于指令B的transclude内容。毕竟,B的超越内容应出于所有意图和目的而存在于A的模板中。问题在于,B的包含内容与A的编译递归分别编译成自己的tranclude链接函数,这不会成为A的链接递归的一部分,因此在链接时不会接收A的translude函数。

    Angular通过以下方式解决此问题:
  • 另外还通过compileNodes递归(而不只是链接递归)对Transclude函数进行了线程化
  • 在编译时将transcludeFn直接传递到transclude内容编译周期
  • 允许CompositeLinkFn从其封闭的compileNodes中调用transclude函数,调用

  • 结论

    我在最初的问题中遇到的问题是通过编译递归对包含函数的这种线程化。链接递归始终正确地将传递函数向下传递给子链接函数,但是编译递归仅在当前节点的指令不具有链接函数的情况下向下传递传递函数,而不管该指令是否传递:
    compileNodes(childNodes, nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
    

    我的问题引起了轰动,这是由于ngStyle创建了一个链接函数,但并没有包含在内。因此突然之间,transclude函数停止了在编译循环中的执行,在ngRepeat编译其子内容时可以在该循环上使用它。 “修复”是将有问题的行更改为:
    compileNodes(childNodes, (nodeLinkFn && nodeLinkFn.transclude) ? nodeLinkFn.transclude : transcludeFn);
    

    基本上,现在只有当它被一个新的,嵌套更深的transclude函数替换时,它才会停止对transclude函数进行线程化(这与Angular文档所说的应该将内容包含在最近的包含transparent的父指令中是一致的)。同样,我不确定是否所有这些都是预期的行为或错误,但是希望无论如何对转写都是有用的? :)

    关于angularjs - ng-repeat中的ng-transclude正在失去对$ transclude函数的访问权限,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22161896/

    10-11 09:17