之所以得到这个,是因为directiveChild
导致directiveParent
嵌套在transclude
中。
我有两个指令
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>
原因是当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
为了避免与角度编译过程发生冲突,我建议将<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>",
}
});
如您所见,此代码更加简洁,因为我们不需要手动处理元素的转换。
[[[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);
}
}
});
如果要把它放在html页面上,请确保不要再次编译它:
如果您需要为每个孩子添加一个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
}
}
});
您在代码中做错了一些事情。我会尝试列出它们:
首先,由于您使用的是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: true
和transclude: true
的答案。这不是工作方式,只要您修复panel1
指令,您的panel指令就可以正常工作。这里是我所做的更正中的一个js小提琴,希望它能按您期望的那样工作。
编辑:
询问是否可以将已包含的内容包装在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手动准确地添加到所需的位置是执行此操作的故障安全方法。这里是小提琴:
之所以得到这个,是因为directiveChild
导致directiveParent
嵌套在transclude
中。
诀窍是directiveChild
意外地使用了与templateUrl
相同的directiveParent
。
之所以得到这个,是因为directiveChild
导致directiveParent
嵌套在transclude
中。