Як я можу динамічно додати директиву в 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/

Відповіді:


259

У вас там безліч безглуздих jQuery, але служба компіляції $ в цьому випадку насправді дуже проста :

.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, такі як датчик, попередження, вкладки тощо. Мабуть, я щось розробив і зараз він працює лише в Chrome, хоча: embed.plnkr.co/WI16H7Rsa5adejXSmyNj/preview
JoshGough

3
Джош - який простіший спосіб досягти цього без використання $compile? Дякуємо за вашу відповідь до речі!
парний виступ

3
@doubleswirve У цьому випадку було б набагато простіше просто використовувати ngRepeat. :-) Але я припускаю, що ви маєте на увазі динамічно додавати нові директиви на сторінку, і в такому випадку відповідь ні - немає більш простого способу, оскільки $compileсервіс - це те, що з'єднує директиви і підключає їх до циклу подій. У $compileподібній ситуації не обійтися, але в більшості випадків інша директива, як ngRepeat, може виконати ту саму роботу (тому ngRepeat робить для нас компіляцію). Чи є у вас конкретний випадок використання?
Джош Девід Міллер

2
Чи не має відбуватися компіляція на етапі попереднього посилання? Я думаю, що контролер повинен містити лише не DOM, код, який можна перевірити, але я новачок у концепції посилання / контролера, тому я не впевнений у собі. Крім того, однією з основних альтернатив є ng-include + частковий + ng-контролер, оскільки він буде діяти як директива з успадкованою областю.
Маркус Роделл

77

На додаток до ідеального прикладу LEE 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

Сподіваюся, що це допомагає.


3
Не забудьте видалити оригінальну директиву, щоб уникнути помилки перевищення максимального розміру стека викликів.
SRachamim

Привіт, будь ласка, надайте ідеї щодо мого нового запропонованого API, щоб зробити програмне додавання директив більш простим процесом? github.com/angular/angular.js/isissue/6950 Дякую!
trusktr

Хочеться, щоб у 2015 році у нас не було обмежень щодо кількості стеків викликів. :(
psycho brm

3
Maximum call stack size exceededПомилка завжди відбувається з - за нескінченною рекурсії. Я ніколи не бачив приклад, коли збільшення розміру стека вирішило б це.
Gunchars

Аналогічна проблема я зіткнувся, Ви можете мені допомогти тут stackoverflow.com/questions/38821980 / ...
Панду дас

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)       

Тепер це добре працює.


1
Я хотів би побачити демонстрацію вставки нового атрибута до елемента, у ванільній JS, якщо можливо - мені щось не вистачає ...
Патрік

реальний приклад вставлення нового атрибута до елемента - ось (див. мій github): github.com/snowyu/angular-reactable/blob/master/src/…
Riceball LEE

1
Не допомагає чесно. Ось так я вирішив свою проблему: stackoverflow.com/a/20137542/1455709
Патрік

Так, цей випадок - це вставлення директиви атрибутів в іншу директиву, а не елемент вставки в шаблон.
Riceball LEE

Які міркування робити це поза шаблоном?
Патрік


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);
            }
        }
    }
}]);

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

Натхненний багатьма попередніми відповідями, я придумав наступну "строманську" директиву, яка замінить себе будь-якими іншими директивами.

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>

Пропіт. Якщо ви не хочете використовувати директиви на основі класів, ви можете змінити '<div></div>'щось, що вам подобається. Наприклад, є фіксований атрибут, який містить ім'я потрібної директиви замість class.


Аналогічна проблема я зіткнувся, Ви можете мені допомогти тут stackoverflow.com/questions/38821980 / ...
Панду дас

О БОЖЕ МІЙ. знадобилося 2 дні, щоб знайти цю компіляцію $ ... дякую друзям ... вона найкраще працює ... AJS you rock ....
Шрінівасан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.