Наследование директив в Angular Light и другие «плюшки»

    Недавно после прочтения одной статьи Армина Роначера, я подумал, что неплохо было бы иметь возможность наследовать директивы и
    вскоре реализовал это в своей библиотеке Angular Light (aLight).

    Вообщем все наследование сводится к разбиению директивы на методы, которые в дальнейшем можно переопределить.
    Вот пример al-show-slow на основе al-show, где происходит медленное появление и скрытие элемента, выглядит так:
    alight.directives.al.showSlow = function(element, name, scope, env) {
        var dir = alight.directives.al.show(element, name, scope, env);  // Создание родительской директивы
        dir.showDom = function() {  // Подменяем "show"
            $(element).fadeIn(1000);
        }
        dir.hideDom = function() {  // Подменяем "hide"
            $(element).fadeOut(1000);
        }
        return dir;
    }
    

    Пример наследования al-show
    Ещё один пример al-value-delay на основе al-value, где данные попадают в модель с задержкой (без повторов).

    Так же тут возможно множественное наследование, но пока не было в этом необходимости.


    Далее ещё несколько полезных методов:

    1) Биндинг к «оторванному» DOM, т.е. тот который не в document:
    tpl = $('<div al-init="i=0" al-click="i+=1">{{i}}</div>')  // Создаем кусок DOM
    alight.applyBindings(null, tpl[0])  // Делаем биндинг
    $('body').append(tpl)  // Для проверки добавляем к body
    

    2) Возможность переименовывания директив, не знаю как в Angular.js, но в aLight это просто:
    alight.directives.al.myKeypress = alight.directives.al.keypress
    

    3) В Angular.js у меня иногда возникал вопрос, как вызвать свой код сразу после $digest/$apply даже если $digest сейчас в процессе обработки. В aLight вместо $digest есть ф-ия $scan, и вызвать свой код после её обработки можно так:
    scope.$scan(function(){
    	// do something
    })
    

    4) Статический биндинг в DOM, в aLight есть 2 способа биндинга значений в DOM:
    	<a href="{{first}}">{{first}}</a>
    	<a href="{{=second}}">{{=second}}</a>
    
    Отличие в том, что во 2-м случае не происходит слежения за изменениями переменной, это удобно в качестве обычного, одноразового рендеринга.

    5) Префиксы у директив. Каждая директива находится в своем «разделе»: al-if, bo-if, ui-if и т.п. Эти разделы позволяют следить за наличием директив (как в knockout.js), например если я напишу:
    <div al-ifn="visible"></div>
    то aLight выдаст ошибку, что такой директивы нет.

    Так же я учел коментарии хабра-пользователей, перевел API aLight с underscore на camelcase, и добавил немного директив, таких как: al-keydown, al-mousedown, al-mousemove. Полный список стандартных директив можно посмотреть тут

    Ещё пара моментов из сегодняшних директив:

    Для al-keydown, al-mousemove и т.п., $event доступен прямо в выражении, подумал что это может быть удобно:
    <input type="text" al-keydown="key = $event.keyCode" />

    Директива al-focused делает двухстороннюю привязку — при установке переменной focused=true, фокус устанавливается на элемент, а при появлении фокуса на элементе, переменная устанавливается в true, при уходе фокуса аналогично.
    <input type="text" al-focused="focused" />
    Пример тут, это удобно для установки фокуса при загрузке страницы или в любой другой момент, но если что-то не устроило, то можно отнаследовать и изменить.

    В основном — все. Замечания и предложения приветствуются.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 7
    • 0
      Что мешает делать наследование директив подобным образом?
      var dir = { // объект директивы };
      app.directive("MyDir1", function(){
          return dir;
      });
      app.directive("MyDir2", function(){
         var dir2 = angular.copy(dir);
         dir2.template="..."; //переопределение чего либо
         return dir2;
      });
      

      Но название Angular Light очень понравилось, я уж подумал google новую версию angular выпустил, пока я спал. :)
      • 0
        Можно, но не очень удобно: как отнаследовать MyDir2, что если MyDir1 изменится, как на счет стандартных директив? Можно сделать более удобные обертки, но все таки неплохо было бы иметь поддержку на уровне фреймворка.
        • 0
          как отнаследовать MyDir2, что если MyDir1 изменится

          Ну здесь все просто, нужно вынести var dir2 = angular.copy(dir); за определение директивы, и повторить наследование.
          как на счет стандартных директив

          Стандартные директивы хорошо спроектированы, и не думаю что у кого-то возникнет необходимость в их переопределении. Но если все же возникнет, есть исходники.
        • 0
          Можно глобально директиву и не хранить, ее можно достать через `$injector`
          app.directive("MyDir1", function(){
              return dir;
          });
          app.directive("MyDir2", function(MyDir1Directive){
             var dir2 = angular.copy(MyDir1Directive);
             //...
             return dir2;
          });
          

          Кстати, стандартные тоже доступны, можно переопределять методы и в них
          • 0
            Кстати, стандартные тоже доступны, можно переопределять методы и в них

            Можете привести пример?
            • +2
              Начну с того, что стандартные директивы и так хорошие, переопределять их не вижу смысла. Но в прошлых версиях Angular был баг в директиве <a></a> и для его исправления пришлось ее пропатчить:

              myApp.run(function(aDirective) {
                  "use strict";
                  aDirective[0].compile = function(element, attr) {
                      // turn <a href ng-click="..">link</a> into a link in IE
                      // but only if it doesn't have name attribute, in which case it's an anchor
                      if (!attr.href) {
                          attr.$set('href', '');
                      }
                      return function(scope, element) {
                          element.bind('click', function(event){
                              // if we have no href url, then don't navigate anywhere.
                              if (!element.attr('href')) {
                                  event.stopPropagation(); // <- не хватало этой строчки
                                  event.preventDefault();
                              }
                         });
                     };
                  };
              });
              


              Еще так можно делать свои шаблоны для директив из библиотеки ui-bootstrap без сборки специальной версии со своими шаблонами:

              myApp.directive('myDatepicker', function(datepickerDirective) {
                  "use strict";
                  return angular.extend({}, datepickerDirective, {
                         templateUrl: 'partitials/datepicker.html' // custom datepicker template
                  });
              });
              


              Но это, как видите, необычные случаи, в обычной ситуации так делать не приходится. Если вам нужно переиспользовать логику стандартной директивы, добавьте ее в поле requires, тогда вы получите доступ к ней через linkFunction. Чтобы реализовать input с задержкой как в вашем примере, нужно написать такую директиву:

              myApp.directive('inputDefer', function($timeout) {
                   return {
                       requires: 'ngModel',
                       link: function(scope, elm, attrs, ngModelCtrl) {
                            var $setViewValue = ngModelCtrl.$setViewValue,
                                delay = attrs.inputDefer;
                            ngModelCtrl.$setViewValue = function() {
                                  var args = arguments;
                                  $timeout(function() {
                                     ngModelCtrl.$setViewValue.apply(ngModelCtrl, args);
                                  }, 500);
                            };
                       }
                   };
              });
              


              Но и это делается непонятно зачем, стандартная реализация ngModel имеет достаточно оптимизаций, чтобы не вызываться слишком часто, а если вы все-таки нашли такой случай, то наверно стоит завести им issue, теперь они обновляются каждую неделю и увидеть ее исправление получится достаточно скоро
        • 0
          Патчить директивы можно ещё следующим образом:

          app.config(function($provide){
              $provide.decorator('someDirective', function($delegate) {
                  $delegate.restrict = 'E';
                  $delegate.controller.method1 = function(){};
                  $delegate.link = function(){};
                  return $delegate;
              }
          })
          

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.