Глобальная связь в модуле angular: шина событий или шаблон посредника/службы

До сих пор я видел много решений проблемы. Самым простым является, конечно, $emit событие в $rootScope как шина событий, например. (https://github.com/btilford/anti-patterns/blob/master/angular/Angular.md)

angular.module('myModule').directive('directiveA', function($rootScope) {
  return {
    link : function($scope, $element) {
      $element.on('click', function(event) {
        $rootScope.$emit('directiveA:clicked', event);
      });
    }
  }
});
angular.module('myModule').directive('directiveB', function() {
  return {
    link : function($scope, $element) {
      $rootScope.on('directiveA:clicked', function(event) {
        console.log('received click event from directiveA');
      });
    }
  }
});

а другой - объявить службу с помощью медиатора или функциональности pubsub/закрытой области действия, например. (Связь между несколькими контроллерами и директивой.)

module.factory('MessageService',
  function() {
    var MessageService = {};

    var listeners = {};
    var count = 0;
    MessageService.registerListener = function(listener) {
      listeners[count] = listener;
      count++;

      return (function(currentCount) {
        return function() {
          delete listeners[currentCount];
        }
      })(count);
    }

    MessageService.broadcastMessage = function(message) {
      var keys = Object.keys(listeners);

      for (var i = 0; i < keys.length; i++) {
        listeners[keys[i]](message);
      }
    }

    return MessageService;
  }
);

Вопрос:

  • Есть ли смысл использовать второй в приложении angular?
  • и какие плюсы и минусы каждого из них сравниваются друг с другом?

Ответ 1

Создание собственной реализации событийного излучателя является контрпродуктивным при написании приложения AngularJS. Angular уже предоставляет все инструменты, необходимые для обмена событиями.

  • Использование $emit on $rootScope прекрасно работает для глобальной межсервисной связи и на самом деле не имеет недостатков.
  • Использование $broadcast в естественной области видимости (связанной с частью DOM) обеспечивает связь между компонентами представления (директивами, контроллерами).
  • Использование $broadcast on $rootScope объединяет две предыдущие точки (это обеспечивает полностью глобальную коммуникационную платформу). Это решение, используемое в основном любой библиотекой на основе AngularJS.

и

  • Если вы беспокоитесь о производительности в предыдущем параметре, и вам действительно нужен отдельный эмитент событий, вы можете легко создать его, создав изолированную область ($rootScope.$new(true)) и используя $broadcast на ней. (Затем вы можете обернуть его в службу и ввести в любое место.)

Последний параметр создает полнофункциональный эмитент событий, интегрированный в Angular (для реализации, предоставленной в вашем вопросе, по крайней мере необходимо будет обернуть все вызовы слушателя в $apply() для правильной интеграции), которые могут быть дополнительно использованы для изменения данных если это подходит для конкретного случая использования.

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


Я не буду вдаваться в подробности о других средствах связи между вашими компонентами. Вообще говоря, когда ситуация требует обмена данными с использованием области видимости, прямого взаимодействия контроллеров или связи через атрибуты DOM Node, вы должны это знать.

Ответ 2

Я бы сказал, что вещание - это способ Angular, как это сделать.

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

Я использовал тот же самый factory, когда вы отправляете сообщение.

angular.module("sharedService", []) 
.factory('MessageService',
  function() {
    var MessageService = {};

    var listeners = {};
    var count = 0;
    MessageService.registerListener = function(listener) {
      listeners[count] = listener;
      count++;

      return (function(currentCount) {
        return function() {
          delete listeners[currentCount];
        };
      })(count);
    };

    MessageService.broadcastMessage = function(message) {
      var keys = Object.keys(listeners);

      for (var i = 0; i < keys.length; i++) {
        listeners[keys[i]](message);
      }
    };

    return MessageService;
  }
)

.directive("directiveA", function(MessageService) {
  return {
    link:function(scope) {
      scope.click = function() {
        MessageService.broadcastMessage("broadcasted message");
      };
    },
    template: '<button ng-click="click()">Click</button>'
  }; 
})
.directive("directiveB", function(MessageService) {
  return {
    link:function(scope) {        
      scope.callback = function(message) {
        console.log(message);
      };

      MessageService.registerListener(scope.callback);
    }
  };
});

Полный пример: http://jsbin.com/mobifuketi/1/edit?html,js,console,output

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

Требовать атрибут

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

.directive("directiveA", function() {
  return {
    require: "^directiveB",

    link: function(scope, element, attrs, directiveCtrl) {

      scope.click = function() {
        directiveCtrl.call();
      };
    },
    template: '<button ng-click="click()">Click</button>'
  }; 
})
.directive("directiveB", function() {
  return {
    controller :function() {
       this.call = function() {

        console.log("method has been called");
      };
    }
  };
});

Полный пример: http://jsbin.com/turoxikute/1/edit?html,js,console,output

Использование $watch

Если функциональность зависит от данных, а не от действий, вы можете использовать $watch и реагировать на изменения данной модели или модели, хранящиеся в совместно используемой службе, ее не похожий на слушателя, ее основную проверку изменений. Я назвал метод changeState() и log "state changed", для всех это ясно.

angular.module("sharedService", []) 
.service("MediatorService", function() {
  this.state = true;

  this.changeState = function() {
     this.state = !this.state;
  };
})

.directive("directiveA", function(MediatorService) {
  return {
    link:function(scope) {

      scope.click = function() {
        MediatorService.changeState();
      };
    },
    template: '<button ng-click="click()">Click</button>'
  }; 
})

.directive("directiveB", function(MediatorService) {
  return {
    link:function(scope) {
        scope.mediator = MediatorService; 
      scope.$watch("mediator.state", function(oldValue, newValue) {
        if (oldValue == newValue) {
          return;
        }  

        console.log("state changed");
      });
    }
  };
});

Полный пример: http://jsbin.com/darefijeto/1/edit?html,js,console,output

Ответ 3

Мне нравится автобус событий.

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

Мне нравится этот пост при использовании postal.js: angular.js с postal.js. Двумя основными преимуществами являются каналы и конверты, которые сделают более ясную, понятную и гибкую логику событий.

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