我有两个指令
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>
答案 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>
是重复的
为避免 与角度编译过程冲突 ,我建议将ng-transclude
的内部html设置为模板或templateUrl属性
您的HTML:
<panel1>
你的JS:
<div data-ng-app="app">
<panel1>
</panel1>
</div>
如您所见,此代码更清晰,因为我们不需要手动转换元素。
已更新 ,其中包含动态添加元素的解决方案,无需使用模板或templateUrl:
app.directive('panel1', function ($compile) {
return {
restrict: "E",
template:"<panel><input type='text' ng-model='firstName'>{{firstName}}</panel>",
}
});
如果你想把它放在html页面上,请确保不要再次编译它:
如果你需要为每个孩子添加一个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
}
});
答案 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: true
和transclude: true
的回答中所说的内容。这不是事情的工作方式,并且您的面板指令很好,只要您修复panel1
指令就应该按预期工作。
这是我所做的更正的js-fiddle,希望它可以按预期工作。
修改强>
有人问你是否可以将被抄送的内容包装在一个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准确地添加到您想要的位置是一种自动防故障方法。
以下是它的小提琴:
答案 2 :(得分:0)
我得到了这个,因为directiveChild
导致我directiveParent
嵌套transclude
。
诀窍是directiveChild
意外使用与templateUrl
相同的directiveParent
。