Подтвердить что ты не робот

Недопустимое использование директивы 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>
4b9b3361

Ответ 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.