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

global communication in angular module: event bus or mediator pattern/service

本文关键字:调解 线或 总线 模式 服务 事件 模块 通信 全局      更新时间:2023-09-26

到目前为止,我已经看到了这个问题的许多解决方案。当然,最简单的方法是将$rootScope中的事件$emit为事件总线,例如 ( 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 应用程序中使用第二个有什么意义吗?
  • 与它们相比,
  • 它们各自的优缺点是什么?

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

  • $rootScope上使用$emit非常适合全局服务间通信,并且实际上没有任何缺点。
  • 在自然作用域(绑定到 DOM 的一部分(上使用$broadcast可在视图组件(指令、控制器(之间提供作用域通信。
  • $rootScope上使用$broadcast将前两点结合在一起(它提供了一个完全全球的交流平台(。这是任何基于 AngularJS 的库基本上使用的解决方案。

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

最后一个选项创建一个集成到 Angular 中的成熟事件发射器(您的问题中提供的实现至少需要将所有侦听器调用包装在 $apply() 中才能正确集成(,如果适合特定用例,可以额外用于数据变化观察。

但是,除非您的应用程序非常庞大,或者您对事件名称冲突非常偏执,否则前三个选项应该足够了。


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

我想说广播是一种Angular方式,如何实现这一目标。

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

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

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((并记录"状态已更改",以便每个人都清楚地看到它。

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

我喜欢活动巴士。

Angular 确实提供了$rootScope$emit,但我认为这不应该约束您将其用于基于事件的流的决定,如果它们很复杂或可预见地复杂。Angular有很多功能,虽然大多数都很棒,但即使是作者也承认它们主要是为了补充好的软件工程原则,而不是取代它们。

我喜欢这篇关于使用邮政.js的文章:带有邮政.js的棱角分明.js事件巴士。 两个主要好处是通道和包络,这将使基于事件的逻辑更加明确、可理解和灵活。

我发现如果状态管理不严格,基于服务的方法很容易出错,这对于异步调用和注入来说很难,您无法确定服务将来将如何多用途。