Добавить директив директивы в AngularJS



Я пытаюсь создать директиву, которая заботится о добавление директивы к элементу, на котором он объявлен.
Например, я хочу построить директиву, которая заботится о добавлении datepicker,datepicker-language и ng-required="true".



если я пытаюсь добавить эти атрибуты, а затем использовать $compile Я, очевидно, создать бесконечный цикл, поэтому я проверяю, если я уже добавил необходимые атрибуты:



angular.module('app')
.directive('superDirective', function ($compile, $injector) {
return {
restrict: 'A',
replace: true,
link: function compile(scope, element, attrs) {
if (element.attr('datepicker')) { // check
return;
}
element.attr('datepicker', 'someValue');
element.attr('datepicker-language', 'en');
// some more
$compile(element)(scope);
}
};
});


конечно, если я не $compile элемента, атрибуты будут установлены, но директива не будет загружена.



это правильный подход или я делаю это неправильно? Есть ли лучший способ добиться такого поведения?



UDPATE: учитывая тот факт, что $compile это единственный способ достичь этого, есть ли способ пропустить первый проход компиляции (элемент может содержать несколько дочерних элементов)? Может быть, установив terminal:true?



обновление 2: я попытался поместить директиву в а select элемент и, как и ожидалось, компиляция выполняется дважды, что означает, что в два раза больше ожидаемого option s.

660   7  

7 ответов:

в тех случаях, когда у вас есть несколько директив на один элемент DOM и где порядок, в котором они применяются вопросы, вы можете использовать priority свойство для того чтобы приказать их приложение. Сначала запускаются более высокие числа. Приоритет по умолчанию равен 0, если вы не укажете его.

EDIT: после обсуждения, вот и все решение. Ключ был к удалить атрибут:element.removeAttr("common-things");, а также element.removeAttr("data-common-things"); (в случае, если пользователи указывают data-common-things в html)

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false, 
      terminal: true, //this setting is important, see explanation below
      priority: 1000, //this setting is important, see explanation below
      compile: function compile(element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        return {
          pre: function preLink(scope, iElement, iAttrs, controller) {  },
          post: function postLink(scope, iElement, iAttrs, controller) {  
            $compile(iElement)(scope);
          }
        };
      }
    };
  });

рабочий плунжер доступен по адресу:http://plnkr.co/edit/Q13bUt?p=preview

или:

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false,
      terminal: true,
      priority: 1000,
      link: function link(scope,element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        $compile(element)(scope);
      }
    };
  });

демо

объяснение, почему мы должны установить terminal: true и priority: 1000 (большое количество):

когда DOM готов, angular ходит по DOM, чтобы идентифицировать все зарегистрированные директивы и компилировать директивы один за другим на основе priorityесли эти директивы на том же элементе. Мы устанавливаем приоритет нашей пользовательской директивы на большое число, чтобы гарантировать, что она будет скомпилирована первый и terminal: true, другие директивы будут пропустить после компиляции этой директивы.

когда наши пользовательские директивы компиляции, она будет изменять элемент путем добавления директив и само удаление и использовать $компиляции сервис, чтобы скомпилировать все директивы (включая те, которые были пропущено).

если мы не ставим terminal:true и priority: 1000, есть вероятность, что некоторые директивы компилируются до наша пользовательская директива. И когда наша пользовательская директива использует $compile для компиляции элемента = > снова скомпилировать уже скомпилированные директивы. Это вызовет непредсказуемое поведение, особенно если директивы, скомпилированные до нашей пользовательской директивы, уже преобразовали DOM.

для получения дополнительной информации о приоритете и терминал, проверьте Как понять "терминал" директивы?

пример директивы, которая также изменяет шаблон ng-repeat (приоритет = 1000), когда ng-repeat компилируется, ng-repeatсделайте копии элемента шаблона перед применением других директив.

благодаря комментарию @Izhaki, вот ссылка на ngRepeat исходный код: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js

вы действительно можете справиться со всем этим с помощью простого тега шаблона. См.http://jsfiddle.net/m4ve9/ для примера. Обратите внимание, что мне на самом деле не нужно свойство compile или link в определении супер-директивы.

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

Если это супер директива, которую необходимо сохранить оригинальный внутренний контент, вы можете использовать transclude : true и заменить внутри <ng-transclude></ng-transclude>

надеюсь, что это поможет, дайте мне знать, если что-то неясно

Алекс

вот решение, которое перемещает директивы, которые должны быть добавлены динамически, в представление, а также добавляет некоторую необязательную (базовую) условную логику. Это сохраняет директиву чистой без жестко закодированной логики.

директива принимает массив объектов, каждый объект содержит имя директивы, которая будет добавлена и значение для передачи к нему (если таковые имеются).

Я изо всех сил пытался придумать прецедент для такой директивы, пока не подумал, что это может быть полезно добавьте некоторую условную логику, которая добавляет только директиву, основанную на некотором условии (хотя ответ ниже все еще придуман). Я добавил необязательный if свойство, которое должно содержать значение bool, выражение или функцию (например, определенную в вашем контроллере), которая определяет, следует ли добавлять директиву или нет.

Я также использую attrs.$attr.dynamicDirectives чтобы получить точное объявление атрибута, используемое для добавления директивы (например,data-dynamic-directive,dynamic-directive) без жесткого кодирования строковых значений для проверки.

Plunker Demo

angular.module('plunker', ['ui.bootstrap'])
    .controller('DatepickerDemoCtrl', ['$scope',
        function($scope) {
            $scope.dt = function() {
                return new Date();
            };
            $scope.selects = [1, 2, 3, 4];
            $scope.el = 2;

            // For use with our dynamic-directive
            $scope.selectIsRequired = true;
            $scope.addTooltip = function() {
                return true;
            };
        }
    ])
    .directive('dynamicDirectives', ['$compile',
        function($compile) {
            
             var addDirectiveToElement = function(scope, element, dir) {
                var propName;
                if (dir.if) {
                    propName = Object.keys(dir)[1];
                    var addDirective = scope.$eval(dir.if);
                    if (addDirective) {
                        element.attr(propName, dir[propName]);
                    }
                } else { // No condition, just add directive
                    propName = Object.keys(dir)[0];
                    element.attr(propName, dir[propName]);
                }
            };
            
            var linker = function(scope, element, attrs) {
                var directives = scope.$eval(attrs.dynamicDirectives);
        
                if (!directives || !angular.isArray(directives)) {
                    return $compile(element)(scope);
                }
               
                // Add all directives in the array
                angular.forEach(directives, function(dir){
                    addDirectiveToElement(scope, element, dir);
                });
                
                // Remove attribute used to add this directive
                element.removeAttr(attrs.$attr.dynamicDirectives);
                // Compile element to run other directives
                $compile(element)(scope);
            };
        
            return {
                priority: 1001, // Run before other directives e.g.  ng-repeat
                terminal: true, // Stop other directives running
                link: linker
            };
        }
    ]);
<!doctype html>
<html ng-app="plunker">

<head>
    <script src="//code.angularjs.org/1.2.20/angular.js"></script>
    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
    <script src="example.js"></script>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>

<body>

    <div data-ng-controller="DatepickerDemoCtrl">

        <select data-ng-options="s for s in selects" data-ng-model="el" 
            data-dynamic-directives="[
                { 'if' : 'selectIsRequired', 'ng-required' : '{{selectIsRequired}}' },
                { 'tooltip-placement' : 'bottom' },
                { 'if' : 'addTooltip()', 'tooltip' : '{{ dt() }}' }
            ]">
            <option value=""></option>
        </select>

    </div>
</body>

</html>

Я хотел добавить свое решение, так как принятый не совсем работает для меня.

Мне нужно было добавить директиву, но также сохранить мою на элементе.

в этом примере я добавляю простую директиву ng-style к элементу. Чтобы предотвратить бесконечные циклы компиляции и позволить мне сохранить мою директиву, я добавил проверку, чтобы увидеть, присутствует ли то, что я добавил, перед перекомпиляцией элемента.

angular.module('some.directive', [])
.directive('someDirective', ['$compile',function($compile){
    return {
        priority: 1001,
        controller: ['$scope', '$element', '$attrs', '$transclude' ,function($scope, $element, $attrs, $transclude) {

            // controller code here

        }],
        compile: function(element, attributes){
            var compile = false;

            //check to see if the target directive was already added
            if(!element.attr('ng-style')){
                //add the target directive
                element.attr('ng-style', "{'width':'200px'}");
                compile = true;
            }
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {  },
                post: function postLink(scope, iElement, iAttrs, controller) {
                    if(compile){
                        $compile(iElement)(scope);
                    }
                }
            };
        }
    };
}]);

попробуйте сохранить состояние в атрибуте самого элемента, например superDirectiveStatus="true"

например:

angular.module('app')
  .directive('superDirective', function ($compile, $injector) {
    return {
      restrict: 'A',
      replace: true,
      link: function compile(scope, element, attrs) {
        if (element.attr('datepicker')) { // check
          return;
        }
        var status = element.attr('superDirectiveStatus');
        if( status !== "true" ){
             element.attr('datepicker', 'someValue');
             element.attr('datepicker-language', 'en');
             // some more
             element.attr('superDirectiveStatus','true');
             $compile(element)(scope);

        }

      }
    };
  });

Я надеюсь, что это поможет вам.

произошло изменение с 1.3.x-1.4.x.

В Угловых 1.3.х это работает:

var dir: ng.IDirective = {
    restrict: "A",
    require: ["select", "ngModel"],
    compile: compile,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
        attributes["ngOptions"] = "a.ID as a.Bezeichnung for a in akademischetitel";
        scope.akademischetitel = AkademischerTitel.query();
    }
}

теперь в угловой 1.4.х мы должны сделать это:

var dir: ng.IDirective = {
    restrict: "A",
    compile: compile,
    terminal: true,
    priority: 10,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");
    tElement.removeAttr("tq-akademischer-titel-select");
    tElement.attr("ng-options", "a.ID as a.Bezeichnung for a in akademischetitel");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {

        $compile(element)(scope);
        scope.akademischetitel = AkademischerTitel.query();
    }
}

(из принятого ответа:https://stackoverflow.com/a/19228302/605586 от хана до).

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

что-то вроде...

link: function(scope, elem, attr){
    var wrapper = angular.element('<div tooltip></div>');
    elem.before(wrapper);
    $compile(wrapper)(scope);
    wrapper.append(elem);
}

это решение имеет то преимущество, что оно сохраняет вещи простыми, не перекомпилируя исходный элемент.

это не сработает, если какая-либо из добавленных директив require любая из директив исходного элемента или если исходный элемент имеет абсолютное позиционирование.

Comments

    Ничего не найдено.