在模板中非法使用ngTransclude指令

时间:2013-12-21 10:44:08

标签: angularjs angularjs-directive

我有两个指令

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指令!没有找到需要转换的父指令。元素:<div class="ng-scope" ng-transclude="">

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

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

修改 我创建了一个jsfiddle页面。提前谢谢。

修改

I my real app panel1 does something like this:

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

result =&gt;

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

3 个答案:

答案 0 :(得分:36)

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

这意味着当您致电$compile(clone.children()[0])(scope)时,您clone.children()[0]的{​​{1}} 已经被 转换为 角。  <panel>已成为:

clone.children()

(面板元素已被 删除并替换 )。

您使用<div ng-transclude="">fsafsafasdf</div>编译普通div也是一样的。当您使用ng-transclude编译普通div时,angular会抛出异常,如文档中所述:

  

当您忘记设置转码时,通常会发生此错误:   在某些指令定义中为true,然后在中使用ngTransclude   指令的模板。

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

即使您将ng-transclude设置为保留replace:false,有时您也会看到这样的转换元素:

<panel>

这也是有问题的,因为<panel class="ng-scope"><div ng-transclude=""><div ng-transclude="" class="ng-scope"><div ng-transclude="" class="ng-scope">fsafsafasdf</div></div></div></panel>是重复的

DEMO

为避免 与角度编译过程冲突 ,我建议将ng-transclude的内部html设置为模板或templateUrl属性

您的HTML:

<panel1>

你的JS:

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

        </panel1>
    </div>

如您所见,此代码更清晰,因为我们不需要手动转换元素。

DEMO

已更新 ,其中包含动态添加元素的解决方案,无需使用模板或templateUrl:

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

            }
        });

DEMO

如果你想把它放在html页面上,请确保不要再次编译它:

DEMO

如果你需要为每个孩子添加一个div。只需使用开箱即用的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); } } });

ng-transclude

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

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

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

答案 1 :(得分:7)

您在代码中做了一些错误。我会尝试列出它们:

首先,由于您使用的是角度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的工作原理。

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

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

此外,当您尝试插入已转换的DOM时,您将转到父级并尝试将其添加到其中。通常,指令应该将它们的dom操作限制在它们自己的元素和下面,而不是尝试修改父dom。这可以极大地混淆按顺序和层次遍历DOM的角度。

根据您的目的来判断,更简单的方法是使用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/

答案 2 :(得分:0)

我得到了这个,因为directiveChild导致我directiveParent嵌套transclude

诀窍是directiveChild意外使用与templateUrl相同的directiveParent