角度模块中的全局通信:事件总线或中介模式/服务

时间:2014-12-08 10:49:34

标签: javascript angularjs design-patterns publish-subscribe mediator

到目前为止,我已经看到了许多问题的解决方案。当然,最简单的一个是$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');
      });
    }
  }
});

另一个是使用mediator或pubsub功能/封闭范围声明服务,例如(Communicating between a Multiple Controllers and a directive。)

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;
  }
);

问题是:

  • 是否有意在角度应用中使用第二个?
  • 每个人相互比较的利弊是什么?

3 个答案:

答案 0 :(得分:15)

在编写AngularJS应用程序时,创建自己的事件发射器实现会产生反作用。 Angular已经提供了基于事件的通信所需的所有工具。

  • $emit上使用$rootScope可以很好地进行全球跨服务通信,并且没有任何缺点。
  • 在自然范围(绑定到DOM的一部分)上使用$broadcast提供视图组件(指令,控制器)之间的范围通信。
  • $broadcast上使用$rootScope将前两个点合在一起(它提供了一个完全全球化的通信平台)。 这是基本上由任何基于AngularJS的库使用的解决方案。

  • 如果您担心上一个选项中的效果,并且您真的想要单独的事件发射器,则可以通过创建隔离范围($rootScope.$new(true))并使用$broadcast轻松创建一个它。 (然后,您可以将其包装到服务中并将其注入任何您想要的位置。)

最后一个选项创建了一个集成到Angular中的完整事件发射器(您的问题中提供的实现至少需要包含$apply()中的所有侦听器调用以进行正确集成),这些调用可以另外用于数据更改观察,如果符合特定的用例。

但是,除非您的申请真的很大,或者您对事件名称冲突真的很偏执,否则前三个选项就足够了。


我不会详细介绍组件之间的其他通信方式。一般来说,当情况需要使用范围,控制器的直接交互或通过DOM节点属性进行通信来进行数据共享时,您应该知道它。

答案 1 :(得分:10)

我想说广播是一种角度方式如何实现这一点。

但是你的中介可以工作,如果你传递指令的内部功能,例如我在范围内使用了方法,但也可以用控制器方法完成。

我使用了与您发布完全相同的工厂。

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

为了完成,我想补充一点,角度也提供了更多的可能性,指令如何沟通。

需要属性

如果您的指令在层次结构中连接,那么您可以使用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&#34;状态已更改&#34;因为每个人都看得很清楚。

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

答案 2 :(得分:3)

我喜欢活动巴士。

Angular确实在$ rootScope上提供了$ emit,但我不认为如果它们复杂或可预见的复杂,那么你决定将它用于基于事件的流。 Angular有很多功能,虽然大多数都很棒,但即使作者承认它们主要是为了恭维良好的软件工程原理,而不是取代它们。

我喜欢这篇关于使用postal.jsAn angular.js event bus with postal.js的帖子。两个主要的好处是通道和包络,这将使更明确,可理解和灵活的基于事件的逻辑。

如果状态未得到严密管理,我发现基于服务的方法容易出错,这对于异步调用和注入很难实现,在这种情况下,您无法确定服务将来如何实现多用途。