Недопустимое использование директивы ngTransclude в шаблоне
У меня есть две директивы
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 = >
<div>
<div class="x"><input type="text></div>
<div class="x"><input type="text></div>
<!--other elements or directive wrapped in div -->
</div>
Ответы
Ответ 1
Причина заключается в том, когда DOM завершена загрузка, angular будет проходить через DOM и преобразовать все директивы в свой шаблон до, вызывающий функцию компиляции и ссылки.
Это означает, что когда вы вызываете $compile(clone.children()[0])(scope)
, clone.children()[0]
, который является вашим <panel>
, в этом случае уже преобразован на angular.
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
Чтобы избежать противоречия с процессом компиляции angular, я рекомендую установить внутренний html <panel1>
как свойство 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
Обновлено с возможностью динамического добавления элементов без использования шаблона или шаблонаUrl:
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
Ответ 2
Вы делаете несколько неправильных действий в своем коде. Я попытаюсь их перечислить:
Во-первых, поскольку вы используете angular 1.2.6, вы больше не должны использовать transclude (функцию компоновщика) в качестве параметра функции компиляции. Это устарело и теперь должно быть передано в качестве 5-го параметра вашей функции связи:
compile: function (element, attr) {
return function (scope, element, attr, ctrl, linker) {
....};
Это не вызывает особую проблему, которую вы видите, но это хорошая практика, чтобы прекратить использование устаревшего синтаксиса.
Реальная проблема заключается в том, как вы применяете свою функцию transclude в директиве panel1
:
parent.prepend($compile(clone.children()[0])(scope));
Прежде чем перейти к тому, что неправильно, давайте быстро рассмотрим, как работает transclude.
Всякий раз, когда в директиве используется переход, транслируемый контент удаляется из dom. Но это скомпилированное содержимое доступно через функцию, переданную в качестве 5-го параметра вашей функции ссылок (обычно называемой функцией переключить).
Ключ состоит в том, что содержимое скомпилировано. Это означает, что вы не должны вызывать $compile на dom, переданном вашему transclude.
Кроме того, когда вы пытаетесь вставить свою переведенную DOM, вы направляетесь к родительскому объекту и пытаетесь добавить его туда. Как правило, директивы должны ограничивать их манипуляции с dom до их собственного элемента и ниже, а не пытаться изменить родительский dom. Это может сильно запутать angular, который перемещается по DOM по порядку и иерархически.
Судя по тому, что вы пытаетесь сделать, более простой способ сделать это - использовать transclude: true
вместо transclude: 'element'
. Объясним разницу:
transclude: 'element'
удалит сам элемент из DOM и вернет весь элемент назад, когда вы вызовете функцию transclude.
transclude: true
просто удалит дочерние элементы элемента из dom и вернет вам детей, когда вы вызовете ваше переключение.
Поскольку вам кажется, что вы заботитесь только о детях, вы должны использовать transclude true (вместо того, чтобы получать детей() из вашего клона). Затем вы можете просто заменить элемент на него детьми (поэтому не вставать и возиться с родительским 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-скрипт исправлений, которые я, надеюсь, работает так, как вы ожидаете.
http://jsfiddle.net/77Spt/3/
EDIT:
Было задано вопрос, можете ли вы перенести переведенный контент в div. Самый простой способ - просто использовать шаблон, как вы делаете в своей другой директиве (идентификатор в шаблоне - это просто, чтобы вы могли видеть его в 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/
Ответ 3
Я получил это, потому что я имел directiveChild
, вложенный в directiveParent
в результате transclude
.
Фокус в том, что directiveChild
случайно использовал тот же templateUrl
как directiveParent
.