模板中非法使用ngTransclude指令

问题描述 投票:23回答:3

我有两个指令

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页面。预先谢谢你。

编辑

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>
angularjs angularjs-directive
3个回答
38
投票

原因是当DOM完成加载后,angular将遍历DOM并将所有指令转换为模板before并调用compile和link函数。

这意味着当您调用$compile(clone.children()[0])(scope)时,在这种情况下为clone.children()[0]<panel> 已经变换按角度。clone.children()已成为:

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

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

与使用ng-transclude编译普通div相同。当您使用ng-transclude编译普通div时,角度抛出异常,如文档中所述:

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

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

为了避免与角度编译过程发生冲突,我建议将<panel1>的内部html设置为template或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

[[[Updated]带有不使用template或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


8
投票

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

首先,由于您使用的是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));

在我出了什么问题之前,让我们快速回顾一下超越的工作原理。

[每当指令使用包含时,被包含的内容就会从dom中删除。但是它的编译内容可以通过作为链接函数的第5个参数传入的函数(通常称为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指令,您的panel指令就可以正常工作。

这里是我所做的更正中的一个js小提琴,希望它能按您期望的那样工作。

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>"          
       }
   });

或者如果您想使用超越功能(我的个人喜好):

   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/

之所以得到这个,是因为directiveChild导致directiveParent嵌套在transclude中。

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


0
投票

之所以得到这个,是因为directiveChild导致directiveParent嵌套在transclude中。

© www.soinside.com 2019 - 2024. All rights reserved.