Как я могу динамически добавить директиву в AngularJS?

212

У меня есть очень упрощенная версия того, что я делаю, и эта проблема решается.

У меня все просто directive. Каждый раз, когда вы нажимаете на элемент, он добавляет еще один. Тем не менее, он должен быть скомпилирован в первую очередь, чтобы правильно отобразить.

Мои исследования привели меня к $compile. Но во всех примерах используется сложная структура, которую я не знаю, как применить здесь.

Скрипки здесь: http://jsfiddle.net/paulocoelho/fBjbP/1/

И JS здесь:

var module = angular.module('testApp', [])
    .directive('test', function () {
    return {
        restrict: 'E',
        template: '<p>{{text}}</p>',
        scope: {
            text: '@text'
        },
        link:function(scope,element){
            $( element ).click(function(){
                // TODO: This does not do what it's supposed to :(
                $(this).parent().append("<test text='n'></test>");
            });
        }
    };
});

Решение Джоша Дэвида Миллера: http://jsfiddle.net/paulocoelho/fBjbP/2/

PCoelho
источник

Ответы:

259

У вас там много бессмысленного jQuery, но сервис $ compile на самом деле очень прост в этом случае:

.directive( 'test', function ( $compile ) {
  return {
    restrict: 'E',
    scope: { text: '@' },
    template: '<p ng-click="add()">{{text}}</p>',
    controller: function ( $scope, $element ) {
      $scope.add = function () {
        var el = $compile( "<test text='n'></test>" )( $scope );
        $element.parent().append( el );
      };
    }
  };
});

Вы заметите, что я также изменил вашу директиву, чтобы следовать рекомендациям. Дайте мне знать, если у вас есть вопросы по любому из них.

Джош Дэвид Миллер
источник
34
Потрясающие. Оно работает. Видите, эти простые и базовые примеры - это те, которые должны быть показаны в документах на английском языке. Они начинают со сложных примеров.
PCoelho
1
Спасибо, Джош, это было действительно полезно. Я создал инструмент в Plnkr, который мы используем в новом CoderDojo, чтобы помочь детям научиться кодировать, и я просто расширил его, чтобы теперь я мог использовать директивы Angular Bootstrap, такие как datepicker, alert, tabs и т. Д. и сейчас он работает только в Chrome: embed.plnkr.co/WI16H7Rsa5adejXSmyNj/preview
JoshGough
3
Джош - что проще сделать без использования $compile? Спасибо за ваш ответ, кстати!
DoubleSirve
3
@doubleswirve В этом случае было бы намного проще просто использовать ngRepeat. :-) Но я предполагаю, что вы имеете в виду динамическое добавление новых директив на страницу, и в этом случае ответ отрицательный - нет более простого пути, потому что $compileслужба - это то, что связывает директивы и подключает их к циклу событий. В $compileтакой ситуации нет никакого способа обойтись, но в большинстве случаев другая директива, такая как ngRepeat, может выполнить ту же работу (поэтому ngRepeat выполняет компиляцию за нас). У вас есть конкретный вариант использования?
Джош Дэвид Миллер
2
Разве компиляция не должна происходить на этапе предварительной ссылки? Я думаю, что контроллер должен содержать не-DOM, тестируемый модулем код, но я новичок в концепции связи / контроллера, поэтому я сам не уверен. Кроме того, одной базовой альтернативой является ng-include + частичный + ng-контроллер, поскольку он будет действовать как директива с унаследованной областью действия.
Маркус Роделл
77

В дополнение к прекрасному примеру Riceball LEE по добавлению новой директивы элемента

newElement = $compile("<div my-directive='n'></div>")($scope)
$element.parent().append(newElement)

Добавление новой директивы атрибута к существующему элементу может быть сделано следующим образом:

Допустим, вы хотите добавить на лету my-directiveк spanэлементу.

template: '<div>Hello <span>World</span></div>'

link: ($scope, $element, $attrs) ->

  span = $element.find('span').clone()
  span.attr('my-directive', 'my-directive')
  span = $compile(span)($scope)
  $element.find('span').replaceWith span

Надеюсь, это поможет.

deadrunk
источник
3
Не забудьте удалить исходную директиву, чтобы предотвратить ошибку «Превышен максимальный размер стека вызовов».
SRachamim
Привет, не могли бы вы представить идеи моего нового предложенного API, чтобы упростить процесс добавления директив программным способом? github.com/angular/angular.js/issues/6950 Спасибо!
trusktr
Я бы хотел, чтобы в 2015 году у нас не было ограничений на размер стека вызовов. :(
psycho brm
3
Maximum call stack size exceededОшибка всегда происходит из - за бесконечной рекурсии. Я никогда не видел случая, чтобы увеличение размера стека решило бы это.
Gunchars
Подобная проблема, с которой я сталкиваюсь, можете ли вы помочь мне здесь stackoverflow.com/questions/38821980/…
pandu das
45

Динамическое добавление директив на angularjs имеет два стиля:

Добавьте директиву angularjs в другую директиву

  • вставка нового элемента (директива)
  • вставка нового атрибута (директивы) в элемент

вставка нового элемента (директива)

это просто. И вы можете использовать в «ссылку» или «компилировать».

var newElement = $compile( "<div my-diretive='n'></div>" )( $scope );
$element.parent().append( newElement );

вставка нового атрибута в элемент

Это тяжело, и у меня болит голова в течение двух дней.

Использование «$ compile» вызовет критическую рекурсивную ошибку !! Возможно, он должен игнорировать текущую директиву при повторной компиляции элемента.

$element.$set("myDirective", "expression");
var newElement = $compile( $element )( $scope ); // critical recursive error.
var newElement = angular.copy(element);          // the same error too.
$element.replaceWith( newElement );

Итак, я должен найти способ вызвать директиву «link». Очень трудно найти полезные методы, которые скрыты глубоко внутри замыканий.

compile: (tElement, tAttrs, transclude) ->
   links = []
   myDirectiveLink = $injector.get('myDirective'+'Directive')[0] #this is the way
   links.push myDirectiveLink
   myAnotherDirectiveLink = ($scope, $element, attrs) ->
       #....
   links.push myAnotherDirectiveLink
   return (scope, elm, attrs, ctrl) ->
       for link in links
           link(scope, elm, attrs, ctrl)       

Теперь это хорошо работает.

Riceball LEE
источник
1
Очень хотелось бы увидеть демонстрацию вставки нового атрибута в элемент, если возможно, в vanilla JS - я что-то упустил ...
Патрик
реальный пример вставки нового атрибута в элемент находится здесь (см. мой github): github.com/snowyu/angular-reactable/blob/master/src/…
Riceball LEE
1
Не помогает честно Вот так я и решил свою проблему: stackoverflow.com/a/20137542/1455709
Патрик
Да, это случай вставки директивы атрибута в другую директиву, а не вставки элемента в шаблон.
Riceball LEE
В чем причина того, что вы делаете это вне шаблона?
Патрик
9
function addAttr(scope, el, attrName, attrValue) {
  el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}
user1212212
источник
5

Принятый ответ Джоша Дэвида Миллера прекрасно работает, если вы пытаетесь динамически добавить директиву, использующую встроенный метод template. Однако, если ваша директива использует templateUrlего ответ, ничего не получится. Вот что сработало для меня:

.directive('helperModal', [, "$compile", "$timeout", function ($compile, $timeout) {
    return {
        restrict: 'E',
        replace: true,
        scope: {}, 
        templateUrl: "app/views/modal.html",
        link: function (scope, element, attrs) {
            scope.modalTitle = attrs.modaltitle;
            scope.modalContentDirective = attrs.modalcontentdirective;
        },
        controller: function ($scope, $element, $attrs) {
            if ($attrs.modalcontentdirective != undefined && $attrs.modalcontentdirective != '') {
                var el = $compile($attrs.modalcontentdirective)($scope);
                $timeout(function () {
                    $scope.$digest();
                    $element.find('.modal-body').append(el);
                }, 0);
            }
        }
    }
}]);
ferics2
источник
5

Джош Дэвид Миллер прав.

PCoelho, в случае, если вам интересно, что $compileпроисходит за кулисами и как вывод HTML генерируется из директивы, пожалуйста, посмотрите ниже

$compileСлужбы компилирует фрагмент HTML ( "< test text='n' >< / test >") , который включает в директиве ( «тест» в качестве элемента) и производит функцию. Затем эту функцию можно выполнить с областью действия, чтобы получить «вывод HTML из директивы».

var compileFunction = $compile("< test text='n' > < / test >");
var HtmlOutputFromDirective = compileFunction($scope);

Более подробная информация с примерами полного кода здесь: http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs

Даниал Локман
источник
4

Вдохновленный многими предыдущими ответами, я придумал следующую директиву «stroman», которая заменит себя другими директивами.

app.directive('stroman', function($compile) {
  return {
    link: function(scope, el, attrName) {
      var newElem = angular.element('<div></div>');
      // Copying all of the attributes
      for (let prop in attrName.$attr) {
        newElem.attr(prop, attrName[prop]);
      }
      el.replaceWith($compile(newElem)(scope)); // Replacing
    }
  };
});

Важно: Зарегистрируйте директивы, которые вы хотите использовать restrict: 'C'. Как это:

app.directive('my-directive', function() {
  return {
    restrict: 'C',
    template: 'Hi there',
  };
});

Вы можете использовать как это:

<stroman class="my-directive other-class" randomProperty="8"></stroman>

Чтобы получить это:

<div class="my-directive other-class" randomProperty="8">Hi there</div>

Protip. Если вы не хотите использовать директивы, основанные на классах, вы можете изменить '<div></div>'то, что вам нравится. Например, есть фиксированный атрибут, который содержит имя желаемой директивы вместо class.

Габор Имре
источник
Подобная проблема, с которой я сталкиваюсь, можете ли вы помочь мне здесь stackoverflow.com/questions/38821980/…
pandu das
О, МОЙ БОГ. потребовалось 2 дня, чтобы найти этот $ compile ... спасибо друзья .. он работает лучше всего ... AJS, ты
Srinivasan