在模板中非法使用ngTransclude指令

Alb*_*orz 22 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) {
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

这是我的观点:

<panel1>
    <panel>
        <input type="text" ng-model="firstName" />
    </panel>
</panel1>
Run Code Online (Sandbox Code Playgroud)

错误:[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>
Run Code Online (Sandbox Code Playgroud)

result =>

    <div>
    <div class="x"><input type="text></div>
    <div class="x"><input type="text></div>
<!--other elements or directive wrapped in div -->
    </div>
Run Code Online (Sandbox Code Playgroud)

Kha*_* TO 36

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

这意味着当你打电话时$compile(clone.children()[0])(scope),clone.children()[0]<panel>在这种情况下的那个已经被角度变换了. clone.children()已成为:

<div ng-transclude="">fsafsafasdf</div>

(面板元件已被移除并更换).

你正在用一个正常的div编译它也是一样的ng-transclude.当你编译一个普通的div时ng-transclude,angular会抛出异常,如文档中所述:

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

DEMO(检查控制台看输出)

即使你设置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>

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

DEMO

为避免与角度编译过程冲突,我建议将内部html设置<panel1>为template或templateUrl属性

你的HTML:

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

        </panel1>
    </div>
Run Code Online (Sandbox Code Playgroud)

你的JS:

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

            }
        });
Run Code Online (Sandbox Code Playgroud)

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

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);
                }
            }
        });
Run Code Online (Sandbox Code Playgroud)

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 
            }
        });
Run Code Online (Sandbox Code Playgroud)

演示(您可能需要根据需要调整模板,删除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
                }
            }
        });
Run Code Online (Sandbox Code Playgroud)

DEMO

  • @Alborz:panel和panel1可以独立使用,有时panel1不应该有面板.是的,你几乎可以把任何东西放在panel1里面.(不需要成为小组) (2认同)

dta*_*enc 7

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

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

compile: function (element, attr) {
  return function (scope, element, attr, ctrl, linker) {
  ....};
Run Code Online (Sandbox Code Playgroud)

这不会导致您遇到的特定问题,但停止使用已弃用的语法是一种很好的做法.

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

parent.prepend($compile(clone.children()[0])(scope));
Run Code Online (Sandbox Code Playgroud)

在我发现错误之前,让我们快速回顾一下transclude的工作原理.

只要指令使用了转义,就会从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);
           });
       }
    }
   });
Run Code Online (Sandbox Code Playgroud)

忽略上一个回答中关于replace: true和所说的内容transclude: 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>"          
       }
   });
Run Code Online (Sandbox Code Playgroud)

或者如果你想使用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);
               });
           }
       }
   });
Run Code Online (Sandbox Code Playgroud)

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

这是它的小提琴:

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