Недопустимое использование директивы 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: "", link: function (scope, elem, attrs) { } } });
И это мое мнение:
Ошибка: [ngTransclude: orphan] Незаконное использование директивы ngTransclude в шаблоне! Нет родительской директивы, для которой требуется переключение. Элемент:
- угловой ng-bind-html и директива внутри него
- Угловые директивы - когда и как использовать компиляцию, controller, предварительную ссылку и пост-ссылку
- Как вы можете ограничить значение из ввода с помощью AngularJS?
- Как автокапитализировать первый символ в поле ввода в AngularJS?
- AngularJS оставляет комментарии в HTML: можно ли их удалить?
Я знаю, что панель1 не является практической директивой. Но в моем реальном приложении я тоже сталкиваюсь с этой проблемой.
Я вижу некоторые объяснения по http://docs.angularjs.org/error/ngTransclude:orphan . Но не знаю, почему у меня есть эта ошибка здесь и как ее решить.
EDIT Я создал страницу jsfiddle . Заранее спасибо.
РЕДАКТИРОВАТЬ
I my real app panel1 does something like this:
result =>
- Переменные привязки от Service / Factory to Controllers
- Почему свойство «replace» устарело в директивах AngularJS?
- угловая директива, инкапсулирующая задержку для ng-change
- AngularJS. Преобразование значения тега (время Unix для чтения человеком)
- Установите фокус на первый недопустимый ввод в форме AngularJs
- Как применить директиву AngularJS на основе classа, установленного ng-classом?
- Отложить угловое выполнение часов после $ digest (повышение DOM-события)
- Предварительный просмотр изображения перед загрузкой Angularjs
Причина в том, что DOM закончил загрузку, угловой будет проходить через DOM и преобразовать все директивы в свой шаблон перед вызовом функции компиляции и ссылки.
Это означает, что когда вы вызываете $compile(clone.children()[0])(scope)
, clone.children()[0]
который является вашей
в этом случае уже преобразован угловым. clone.children()
уже становится:
(элемент панели был удален и заменен ).
То же самое происходит и с компиляцией нормального div с ng-transclude
. Когда вы компилируете нормальный div с ng-transclude
, исключение угловых исключений, как говорится в документах:
Эта ошибка часто возникает, когда вы забыли установить transclude: true в некоторых определениях директивы, а затем использовали ngTransclude в шаблоне директивы.
DEMO (проверить консоль, чтобы увидеть выход)
Даже если вы установите replace:false
чтобы сохранить свою
, иногда вы увидите преобразованный элемент следующим образом:
что также проблематично, поскольку ng-transclude
дублируется
DEMO
Чтобы избежать конфликта с процессом угловой компиляции , я рекомендую установить внутренний html
как свойство template или templateUrl
Ваш HTML:
Ваш JS:
app.directive('panel1', function ($compile) { return { restrict: "E", template:"{{firstName}} ", } });
Как вы можете видеть, этот код является более чистым, так как нам не нужно разбираться с переходом элемента вручную.
DEMO
Обновлено с помощью решения для динамического добавления элементов без использования шаблона или шаблонаUrl:
app.directive('panel1', function ($compile) { return { restrict: "E", template:"", link : function(scope,element){ var html = "{{firstName}} "; element.append(html); $compile(element.contents())(scope); } } });
DEMO
Если вы хотите разместить его на странице html, убедитесь, что вы не компилируете его снова:
DEMO
Если вам нужно добавить div для каждого ребенка. Просто используйте out-of the box ng-transclude
.
app.directive('panel1', function ($compile) { return { restrict: "E", replace:true, transclude: true, template:"" //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:"", link: function (scope, elem, attrs) { elem.children().wrap(""); //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
Вы делаете несколько неправильных действий в своем коде. Я попытаюсь их перечислить:
Во-первых, поскольку вы используете угловой 1.2.6, вы больше не должны использовать transclude (функцию компоновщика) в качестве параметра функции компиляции. Это устарело и теперь должно быть передано в качестве 5-го параметра вашей функции связи:
compile: function (element, attr) { return function (scope, element, attr, ctrl, linker) { ....};
Это не вызывает особую проблему, которую вы видите, но рекомендуется прекратить использование устаревшего синтаксиса.
Реальная проблема заключается в том, как вы применяете свою функцию panel1
директиве panel1
:
parent.prepend($compile(clone.children()[0])(scope));
Прежде чем я пойду, что не так, давайте быстро рассмотрим, как работает transclude.
Всякий раз, когда в директиве используется переход, переводимое содержимое удаляется из dom. Но это скомпилированное содержимое доступно через функцию, переданную в качестве 5-го параметра вашей функции ссылок (обычно называемой функцией перехвата).
Ключ состоит в том, что контент компилируется . Это означает, что вы не должны вызывать $ compile на dom, переданном в ваш перехват.
Кроме того, когда вы пытаетесь вставить свою переведенную DOM, вы направляетесь к родительскому объекту и пытаетесь добавить его туда. Как правило, директивы должны ограничивать их манипуляции с dom до их собственного элемента и ниже, а не пытаться изменить родительский dom. Это может сильно запутать угловое устройство, которое перемещается по 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-скрипт исправлений, которые я надеялся, что он работает так, как вы ожидаете.
РЕДАКТИРОВАТЬ:
Было задано вопрос, можете ли вы перенести переведенный контент в div. Самый простой способ – просто использовать шаблон, как в своей другой директиве (идентификатор в шаблоне только для того, чтобы вы могли видеть его в html, он не служит никакой другой цели):
app.directive('panel1', function ($compile) { return { restrict: "E", transclude: true, replace: true, template: "" } });
Или если вы хотите использовать функцию transclude (мои личные предпочтения):
app.directive('panel1', function ($compile) { return { restrict: "E", transclude: true, replace: true, template: "", link: function (scope, element, attr, ctrl, linker) { linker(function (clone) { element.append(clone); }); } } });
Причина, по которой я предпочитаю этот синтаксис, заключается в том, что ng-transclude
– простая и немая директива, которая легко путается. Хотя в этой ситуации это просто, ручное добавление dom точно в том месте, где вы хотите, – это безопасный способ сделать это.
Вот сценарий для него:
Я получил это, потому что у меня была directiveChild
вложенная в directiveParent
в результате transclude
.
Фокус в том, что directiveChild
случайно использовал тот же templateUrl
как directiveParent
.