В AngularJS: что является лучшим способом, чтобы привязать к глобальным событием в директиве


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

каков наилучший подход для этого? Насколько я понимаю, у нас есть два варианта: 1. Пусть каждая директива привязывается к событию и делает это волшебство на текущем элементе 2. Создайте глобальный прослушиватель событий, который выполняет селектор DOM для получения каждого элемента, к которому должна применяться логика.

Вариант 1 имеет преимущество в том, что у вас уже есть доступ к элементу, над которым вы хотите выполнить некоторые операции. Но...вариант 2 имеет то преимущество, что вам не нужно привязывать несколько раз (для каждой директивы) к одному и тому же событию, что может быть преимуществом производительности.

давайте проиллюстрируем оба варианта:

Вариант 1:

angular.module('app').directive('myDirective', function(){

     function doSomethingFancy(el){
         // In here we have our operations on the element
    }

    return {
        link: function(scope, element){
             // Bind to the window resize event for each directive instance.
             angular.element(window).on('resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

Вариант 2:

angular.module('app').directive('myDirective', function(){

    function doSomethingFancy(){
         var elements = document.querySelectorAll('[my-directive]');
         angular.forEach(elements, function(el){
             // In here we have our operations on the element
         });
    }

    return {
        link: function(scope, element){
             // Maybe we have to do something in here, maybe not.
        }
    };

    // Bind to the window resize event only once.
    angular.element(window).on('resize', doSomethingFancy);
});

оба подхода работают нормально, но я чувствую, что вариант два не является действительно 'угловой-иш'.

какие идеи?

5 60

5 ответов:

я выбрал другой метод, чтобы эффективно локализовать глобальные события, такие как изменение размера окна. Он преобразует события Javascript в события угловой области с помощью другой директивы.

app.directive('resize', function($window) {
  return {
    link: function(scope) {
      function onResize(e) {
        // Namespacing events with name of directive + event to avoid collisions
        scope.$broadcast('resize::resize');
      }

      function cleanUp() {
        angular.element($window).off('resize', onResize);
      }

      angular.element($window).on('resize', onResize);
      scope.$on('$destroy', cleanUp);
    }
  }
});

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

<body ng-app="myApp" resize>...

а затем прослушайте событие в других директивах

<div my-directive>....

закодировано как:

app.directive('myDirective', function() {
  return {
    link: function(scope, element) {
      scope.$on('resize::resize', function() {
        doSomethingFancy(element);
      });
    });
  }
});

это имеет ряд преимуществ по сравнению с другими подходы:

  • не хрупкий к точной форме о том, как используются директивы. Ваш вариант 2 требует my-directive когда angular рассматривает следующее как эквивалент:my:directive,data-my-directive,x-my-directive,my_directive как видно в руководство по директивы

  • у вас есть одно место, чтобы точно повлиять на то, как событие Javascript преобразуется в угловое событие, которое затем влияет на всех слушателей. Скажите, что вы позже хотите дебоширить javascript resize событие, с помощью функция lodash debounce. Вы можете изменить

при разработке поверх фреймворка я часто нахожу полезным агностически подумать о проблеме, прежде чем разрабатывать идиоматику. Ответ на "что" и "почему" вытесняет "как".

ответ здесь действительно зависит от сложности doSomethingFancy(). Существуют ли данные, набор функций или объекты домена, связанные с экземплярами этой директивы? Это чисто презентационная забота, как настройка width или height свойства отдельных элементов соответствующие пропорции размера окна? Убедитесь, что вы используете правильный инструмент для работы, не весь швейцарский армейский нож, когда работа требует пинцетом и у вас есть доступ к отдельной паре. Ради продолжения в этом духе я буду оперировать предположением, что doSomethingFancy() - Это чисто презентационная функция.

проблема обертывания глобального события браузера в угловое событие может быть обработана некоторой простой фазой запуска конфигурация:

angular.module('myApp')
    .run(function ($rootScope) {
        angular.element(window).on('resize', function () {
            $rootScope.$broadcast('global:resize');  
        })
    })
;

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

второй концерн работает на n количество элементов при запуске этого события. Опять же, если вам не нужны все колокола и свистки директивы, есть и другие способы сделать это. Вы можете расширить или адаптировать подход в блоке run выше:

angular.module('myApp')
    .run(function () {
        angular.element(window).on('resize', function () {
            var elements = document.querySelectorAll('.reacts-to-resize');
        })
    })
;

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

/**
 * you can inject any services you want: $rootScope if you still want to $broadcast (in)
 * which case, you'd have a "Publisher" instead of a "Mediator"), one or more services 
 * that maintain some domain objects that you want to manipulate, etc.
 */
function ResizeMediator($window) {
    function doSomethingFancy() {
        // whatever fancy stuff you want to do
    }

    angular.element($window).bind('resize', function () {
        // call doSomethingFancy() or maybe some other stuff
    });
}

angular.module('myApp')
    .service('resizeMediator', ResizeMediator)
    .run(resizeMediator)
;

теперь у нас есть инкапсулированная служба, которая может быть протестирована на единицу, но не запускает неиспользуемые фазы выполнения.

пара проблем, которые также будут фактор в решении:

  • мертвые слушателей - С вариантом 1, вы создаете по крайней мере один прослушиватель событий для каждого экземпляра директивы. Если эти элементы динамически добавляются или удаляются из DOM, и вы не вызываете $on('$destroy'), вы рискуете применить обработчики событий, когда их элементы больше не существуют.
  • производительность операторов ширины / высоты - я предполагаю, что есть логика box-model здесь, учитывая, что глобальное событие-это изменение размера браузера. Если нет, проигнорируйте это; если да, то вы хотите быть осторожными в отношении того, к каким свойствам вы обращаетесь и как часто, потому что браузер переплавляет может быть огромный виновник в ухудшении производительности.

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

на мой взгляд, я бы пошел с методом #1 и немного подправил использование сервиса $window.

angular.module('app').directive('myDirective', function($window){

     function doSomethingFancy(el){
         // In here we have our operations on the element
    }

    return {
        link: function(scope, element){
             // Bind to the window resize event for each directive instance.
             anguar.element($window).bind('resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

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

EDIT: чем больше я думаю об этот метод тем больше мне на самом деле начинает нравиться по сравнению с первым... Отличный надежный способ прослушивания события изменения размера окна-возможно, в будущем что - то еще должно "знать" эту информацию, и если вы не сделаете что-то подобное, вы будете вынуждены настроить -еще раз - еще один прослушиватель событий к окну.изменение размера события.

приложение.беги

app.run(function($window, $rootScope) {
  angular.element($window).bind('resize', function(){
     $rootScope.$broadcast('window-resize');
  });
}

директива угловатый.модуль ("приложение").директива ('myDirective', функция ($rootScope) {

     function doSomethingFancy(el){
         // In here we have our operations on the element
    }

    return {
        link: function(scope, element){
             // Bind to the window resize event for each directive instance.
             $rootScope.$on('window-resize', function(){
                  doSomethingFancy(element);
             });
        }
    };
});

наконец-то Удивительный источник того, как делать вещи, чтобы следовать угловым-ui ребята, например ui-bootstrap. Я узнал кучу того, как набивать от этих парней, например, радости обучения модульному тесту в angular. Они обеспечивают большую чистую кодовую базу для проверки.

второй подход кажется более хрупким, так как Angular предлагает много способов ссылаться на директиву в шаблоне (my-directive,my_directive,my:directive,x-my-directive,data-my-directive и т. д.) таким образом, селектор CSS, охватывающий их все, может стать действительно сложным.

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

но я был бы прагматичным. Если вы имеете дело с несколькими экземплярами, пойти с #1. Если у вас есть сотни из них, я бы пошел с #2.

вот один из способов, которым вы могли бы это сделать, просто сохраните свои элементы в массиве, а затем в "глобальное событие" вы можете цикл через элементы и делать то, что вам нужно сделать.

angular.module('app').directive('myDirective', function($window){

    var elements = [];

    $window.on('resize', function(){
       elements.forEach(function(element){
           // In here we have our operations on the element
       });
    });

    return {
        link: function(scope, element){
            elements.push(element);
        }
    };
});