Пользователь
0,1
рейтинг
7 января 2014 в 00:41

Разработка → Наследование директив в 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" />
Пример тут, это удобно для установки фокуса при загрузке страницы или в любой другой момент, но если что-то не устроило, то можно отнаследовать и изменить.

В основном — все. Замечания и предложения приветствуются.
Oleg Nechaev @lega
карма
43,0
рейтинг 0,1
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (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;
        }
    })
    

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