【问题标题】:Illegal use of ngTransclude directive in the template在模板中非法使用 ngTransclude 指令
【发布时间】:2013-12-21 10:44:08
【问题描述】:

我有两个指令

app.directive('panel1', function ($compile) {
    return {
        restrict: "E",
        transclude: 'element',
        compile: function (element, attr, linker) {
            return function (scope, element, attr) {
                var parent = element.parent();
                linker(scope, function (clone) {
                    parent.prepend($compile( clone.children()[0])(scope));//cause error.
                  //  parent.prepend(clone);// This line remove the error but i want to access the children in my real app.
                });
            };
        }
    }
});

app.directive('panel', function ($compile) {
    return {
        restrict: "E",
        replace: true,
        transclude: true,
        template: "<div ng-transclude ></div>",
        link: function (scope, elem, attrs) {
        }
    }
});

这是我的观点:

<panel1>
    <panel>
        <input type="text" ng-model="firstName" />
    </panel>
</panel1>

错误:[ngTransclude:orphan] 在模板中非法使用 ngTransclude 指令!没有找到需要嵌入的父指令。元素:&lt;div class="ng-scope" ng-transclude=""&gt;

我知道 panel1 不是一个实用的指令。但在我的实际应用中,我也遇到了这个问题。

我在http://docs.angularjs.org/error/ngTransclude:orphan 上看到了一些解释。但是我不知道为什么会出现这个错误以及如何解决它。

编辑 我创建了一个jsfiddle 页面。提前谢谢你。

编辑

In my real app panel1 does something like this:

    <panel1>
    <input type="text>
    <input type="text>
<!--other elements or directive-->
    </panel1>

结果 =>

    <div>
    <div class="x"><input type="text></div>
    <div class="x"><input type="text></div>
<!--other elements or directive wrapped in div -->
    </div>

【问题讨论】:

  • 您是否尝试向panel 指令:require: "^panel1" 投放广告?
  • 这两个指令不依赖。但是我在面板指令中添加了 require: "^panel1" 以进行测试,并且错误不会消失。
  • add a div per each children.的另外1个解决方案检查我的更新答案
  • 我在编辑部分添加了解决您的问题的解决方案

标签: angularjs angularjs-directive


【解决方案1】:

原因是当 DOM 加载完成后,angular 会遍历 DOM 并将所有指令转换为模板调用编译和链接函数之前。

这意味着当你调用$compile(clone.children()[0])(scope) 时,clone.children()[0] 在这种情况下是你的&lt;panel&gt; 已经被角度转换了clone.children() 已经变成:

&lt;div ng-transclude=""&gt;fsafsafasdf&lt;/div&gt;

(面板元素已移除和替换)。

这和你用ng-transclude 编译一个普通的div 是一样的。当您使用 ng-transclude 编译普通 div 时,Angular 会抛出异常,如文档中所述:

这个错误经常发生在忘记设置transclude的时候: 在某些指令定义中为 true,然后在 指令的模板。

DEMO(检查控制台查看输出)

即使您将replace:false 设置为保留您的&lt;panel&gt;,有时您也会看到转换后的元素如下:

&lt;panel class="ng-scope"&gt;&lt;div ng-transclude=""&gt;&lt;div ng-transclude="" class="ng-scope"&gt;&lt;div ng-transclude="" class="ng-scope"&gt;fsafsafasdf&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/panel&gt;

这也是有问题的,因为ng-transclude 是重复的

DEMO

为避免与角度编译过程冲突,我建议将&lt;panel1&gt;的内部html设置为模板或templateUrl属性

您的 HTML:

<div data-ng-app="app">
        <panel1>

        </panel1>
    </div>

你的 JS:

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                template:"<panel><input type='text' ng-model='firstName'>{{firstName}}</panel>",

            }
        });

如您所见,这段代码更简洁,因为我们不需要手动处理元素的嵌入。

DEMO

更新解决方案可以在不使用模板或 templateUrl 的情况下动态添加元素:

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                template:"<div></div>",
                link : function(scope,element){
                    var html = "<panel><input type='text' ng-model='firstName'>{{firstName}}</panel>";
                    element.append(html);
                    $compile(element.contents())(scope);
                }
            }
        });

DEMO

如果要放到html页面上,请确保不要再次编译:

DEMO

如果您需要为每个孩子添加一个 div。只需使用开箱即用的ng-transclude

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                replace:true,
                transclude: true,
                template:"<div><div ng-transclude></div></div>" //you could adjust your template to add more nesting divs or remove 
            }
        });

DEMO(您可能需要根据需要调整模板,删除 div 或添加更多 div)

基于 OP 更新问题的解决方案:

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                replace:true,
                transclude: true,
                template:"<div ng-transclude></div>",
                link: function (scope, elem, attrs) {
                    elem.children().wrap("<div>"); //Don't need to use compile here.
                   //Just wrap the children in a div, you could adjust this logic to add class to div depending on your children
                }
            }
        });

DEMO

【讨论】:

  • 感谢您提供有用的解释。但是我的 panel1 没有任何模板,我通过遍历 DOM 添加它的子级。有没有嵌套容器指令的解决方案?实际上我在 panel1 中操作 html 元素。
  • @Alborz:这类似于使用模板,只需在链接函数中创建new html元素并将其添加到panel1并使用$compile编译它。用 DEMO 检查我的更新答案
  • @Alborz:关键是不要把它放在html文件上,因为它会被angular编译,这可能与我们的编译冲突。
  • @Alborz:如果你想把它放在html页面上,确保不要再次编译它。用另外 1 个演示检查我的更新答案。
  • @Alborz: panel 和 panel1 可以独立使用,有时 panel1 里面不应该有 panel。是的,您几乎可以在 panel1 中放置任何东西。 (不需要是面板)
【解决方案2】:

您在代码中做错了一些事情。我将尝试列出它们:

首先,由于您使用的是 Angular 1.2.6,因此您不应再使用 transclude(您的链接器函数)作为编译函数的参数。这已被弃用,现在应该作为第 5 个参数传递给您的链接函数:

compile: function (element, attr) {
  return function (scope, element, attr, ctrl, linker) {
  ....};

这不会导致您看到的特定问题,但最好停止使用已弃用的语法。

真正的问题在于你如何在panel1 指令中应用你的transclude 函数:

parent.prepend($compile(clone.children()[0])(scope));

在讨论问题所在之前,让我们快速回顾一下 transclude 的工作原理。

每当指令使用嵌入时,被嵌入的内容就会从 dom 中删除。但是它的编译内容可以通过作为链接函数的第 5 个参数传入的函数(通常称为 transclude 函数)来访问。

关键是内容是编译的。这意味着你不应该在传递给你的 transclude 的 dom 上调用 $compile。

此外,当您尝试插入嵌入的 DOM 时,您将前往父级并尝试将其添加到那里。通常指令应该将它们的 dom 操作限制在它们自己的元素和下面,而不是尝试修改父 dom。这会极大地混淆按顺序和分层遍历 DOM 的 Angular。

从你想要做的事情来看,更简单的方法是使用transclude: true 而不是transclude: 'element'。让我们解释一下区别:

transclude: 'element' 将从 DOM 中移除元素本身,并在您调用 transclude 函数时将整个元素归还给您。

transclude: true 只会从 dom 中删除元素的子元素,并在您调用 transclude 时将子元素返回给您。

由于您似乎只关心孩子,您应该使用 transclude true (而不是从您的克隆中获取 children() )。然后你可以简单地用它的子元素替换元素(因此不会上去和父 dom 搞乱)。

最后,除非您有充分的理由这样做(通常被嵌入的内容应保持其原始范围),否则覆盖转入函数的范围不是一个好习惯。因此,当您调用 linker() 时,我会避免传递范围。

您的最终简化指令应如下所示:

   app.directive('panel1', function ($compile) {
     return {
       restrict: "E",
       transclude: true,
       link: function (scope, element, attr, ctrl, linker) {
           linker(function (clone) {
               element.replaceWith(clone);
           });
       }
    }
   });

忽略上一个答案中关于replace: truetransclude: true 的内容。这不是事情的运作方式,您的面板指令很好,只要您修复 panel1 指令,它就应该按预期工作。

这是我所做的更正的 js-fiddle,希望它能按您的预期工作。

http://jsfiddle.net/77Spt/3/

编辑:

有人问你是否可以将嵌入的内容包装在一个 div 中。最简单的方法是像在其他指令中一样使用模板(模板中的 id 只是为了让您可以在 html 中看到它,它没有其他用途):

   app.directive('panel1', function ($compile) {
       return {
           restrict: "E",
           transclude: true,
           replace: true,
           template: "<div id='wrappingDiv' ng-transclude></div>"          
       }
   });

或者如果你想使用 transclude 功能(我个人喜好):

   app.directive('panel1', function ($compile) {
       return {
           restrict: "E",
           transclude: true,
           replace: true,
           template: "<div id='wrappingDiv'></div>",
           link: function (scope, element, attr, ctrl, linker) {
               linker(function (clone) {
                   element.append(clone);
               });
           }
       }
   });

我喜欢这种语法的原因是ng-transclude 是一个简单而愚蠢的指令,很容易混淆。虽然在这种情况下很简单,但手动将 dom 添加到您想要的位置是一种安全的方法。

这是它的小提琴:

http://jsfiddle.net/77Spt/6/

【讨论】:

  • 假设我想将每个孩子包装在一个 div 中。是否可以?您不会更改示例中的孩子。
  • 是否可以在不使用模板的情况下将其包装在 div 中?我的意思是操纵孩子。查看我的最后一次编辑。
  • 当然,你可以创建一个 div 节点,将 dom 附加到它上面,然后用它替换元素。唯一的限制是 div 上不应该有任何指令。如果你需要 div 有指令,你需要创建 div,编译它,链接它,然后将嵌入的 dom 附加到它。
  • @Alborz:The key is that the content is compiled (I already said that in my answer)。关于解决方案,有一堆解决方案as long as you do not compile again the already compiled DOM。很难说哪个是最好的。最好的解决方案应该基于您要解决的问题。(不应基于个人喜好)。我指出了一堆解决方案,包括模板、在链接函数中创建 DOM、嵌套指令……并让 OP 决定哪个符合他的要求。
  • 所以这是我的主要问题。 panel1 可能包含其他指令,我需要编译它。因为我为每个孩子添加了一个 div。有什么解决办法吗?
【解决方案3】:

我得到这个是因为 transclude 导致 directiveChild 嵌套在 directiveParent 中。

诀窍在于directiveChild 不小心使用了与directiveParent 相同的templateUrl

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-22
    • 1970-01-01
    • 2018-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多