AngularJS中控制器之间的正确通信方式是什么?

什么是控制器之间正确的通信方式?

我目前正在使用一个涉及window的可怕的软糖:

 function StockSubgroupCtrl($scope, $http) { $scope.subgroups = []; $scope.handleSubgroupsLoaded = function(data, status) { $scope.subgroups = data; } $scope.fetch = function(prod_grp) { $http.get('/api/stock/groups/' + prod_grp + '/subgroups/').success($scope.handleSubgroupsLoaded); } window.fetchStockSubgroups = $scope.fetch; } function StockGroupCtrl($scope, $http) { ... $scope.select = function(prod_grp) { $scope.selectedGroup = prod_grp; window.fetchStockSubgroups(prod_grp); } } 

编辑 :在这个答案解决的问题已经在angular.js 版本1.2.7中解决。 $broadcast现在可以避免在未注册的作用域上冒泡,运行速度与$ emit类似。 $广播表演与角度为1.2.16的$ emit相同

所以,现在你可以:

  • $rootScope使用$broadcast
  • 使用需要了解事件的本地$scope中的$on监听

下面的原始答案

我强烈build议不要使用$rootScope.$broadcast + $scope.$on ,而是使用$rootScope.$emit + $rootScope.$on 。 前者可能会导致@numan引发的严重的性能问题。 那是因为这个事件会在所有的范围内都下降。

然而,后者(使用$rootScope.$emit + $rootScope.$on )不会因此受到影响,因此可以用作快速通信通道!

$emit的angular度文档:

通过范围层次向上调度事件名称,通知注册

由于$rootScope之上没有作用域,因此不会冒泡。 使用$rootScope.$emit() / $rootScope.$on()作为EventBus是完全安全的。

但是,从控制器中使用它时有一个问题。 如果直接绑定到控制器内的$rootScope.$on() ,则必须在本地$scope被销毁时清理绑定。 这是因为控制器(与服务相反)可以在应用程序的生命周期中多次实例化,这会导致绑定总结,最终在所有地方创build内存泄漏:)

要取消注册,只需监听$scope$destroy事件,然后调用$rootScope.$on返回的函数。

 angular .module('MyApp') .controller('MyController', ['$scope', '$rootScope', function MyController($scope, $rootScope) { var unbind = $rootScope.$on('someComponent.someCrazyEvent', function(){ console.log('foo'); }); $scope.$on('$destroy', unbind); } ]); 

我会说,这不是一个真正的angular度特定的事情,因为它也适用于其他的EventBus实现,所以你必须清理资源。

但是,您可以使这些情况下的生活更轻松。 例如,你可以猴子修改$rootScope并给它一个$onRootScope来订阅在$rootScope上发出的事件,但是当本地的$scope被销毁的时候也直接清理这个处理器。

猴子修补$rootScope来提供这样的$onRootScope方法最$onRootScope方法是通过一个装饰器(一个运行块可能会做得很好,但是pssst,不要告诉任何人)

为了确保$onRootScope属性在枚举超出$scope时不会出现意外,我们使用Object.defineProperty()并将enumerable设置为false 。 请记住,您可能需要一个ES5垫片。

 angular .module('MyApp') .config(['$provide', function($provide){ $provide.decorator('$rootScope', ['$delegate', function($delegate){ Object.defineProperty($delegate.constructor.prototype, '$onRootScope', { value: function(name, listener){ var unsubscribe = $delegate.$on(name, listener); this.$on('$destroy', unsubscribe); return unsubscribe; }, enumerable: false }); return $delegate; }]); }]); 

使用这种方法,上面的控制器代码可以简化为:

 angular .module('MyApp') .controller('MyController', ['$scope', function MyController($scope) { $scope.$onRootScope('someComponent.someCrazyEvent', function(){ console.log('foo'); }); } ]); 

所以作为所有这些的最终结果,我强烈build议你使用$rootScope.$emit + $scope.$onRootScope

顺便说一句,我试图说服angular度团队解决angular内核的问题。 这里有一个讨论: https : //github.com/angular/angular.js/issues/4574

这是一个jsperf,它显示了$broadcast带来的良好效果,只需要100 $scope就可以带来多less效果。

http://jsperf.com/rootscope-emit-vs-rootscope-broadcast

jsperf的结果

这里最好的答案是从@ozmalifeguard已经提到的一个angular度问题中解决(至less在版本> 1.2.16和“可能更早的版本”)。 但是,我没有真正的解决scheme,只是读了所有这些答案。

在我看来,现在的答案应该是

  • $rootScope使用$broadcast
  • 使用需要了解事件的本地$scope中的$on监听

所以要发表

 // EXAMPLE PUBLISHER angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope', function ($rootScope, $scope) { $rootScope.$broadcast('topic', 'message'); }]); 

并订阅

 // EXAMPLE SUBSCRIBER angular.module('test').controller('ctrlSubscribe', ['$scope', function ($scope) { $scope.$on('topic', function (event, arg) { $scope.receiver = 'got your ' + arg; }); }]); 

Plunkers

  • Regular $ scope语法 (如上所示)
  • 新的Controller As语法

如果将监听程序注册到本地$scope ,则在关联的控制器被删除时,它将被$destroy本身自动$destroy

对于PubSub通信,使用$ rootScope。$ broadcast和$ scope。$ on。

另外,看到这个职位: AngularJS – 控制器之间进行通信

由于defineProperty具有浏览器兼容性问题,我认为我们可以考虑使用服务。

 angular.module('myservice', [], function($provide) { $provide.factory('msgBus', ['$rootScope', function($rootScope) { var msgBus = {}; msgBus.emitMsg = function(msg) { $rootScope.$emit(msg); }; msgBus.onMsg = function(msg, scope, func) { var unbind = $rootScope.$on(msg, func); scope.$on('$destroy', unbind); }; return msgBus; }]); }); 

并像这样在控制器中使用它:

  • 控制器1

     function($scope, msgBus) { $scope.sendmsg = function() { msgBus.emitMsg('somemsg') } } 
  • 控制器2

     function($scope, msgBus) { msgBus.onMsg('somemsg', $scope, function() { // your logic }); } 

GridLinked发布了一个PubSub解决scheme,看起来devise的很好。 服务可以在这里find。

他们的服务也是一个图表:

消息服务

实际上使用发射和广播是低效的,因为事件在范围层次上下起伏,容易退化为复杂应用的性能瓶颈。

我会build议使用一项服务。 这是我最近如何在我的一个项目中实现它 – https://gist.github.com/3384419

基本理念 – 注册一个酒吧/活动巴士作为服务。 然后在需要订阅或发布事件/主题的地方注入该事件总线。

在服务中使用get和set方法可以很容易地在控制器之间传递消息。

 var myApp = angular.module("myApp",[]); myApp.factory('myFactoryService',function(){ var data=""; return{ setData:function(str){ data = str; }, getData:function(){ return data; } } }) myApp.controller('FirstController',function($scope,myFactoryService){ myFactoryService.setData("Im am set in first controller"); }); myApp.controller('SecondController',function($scope,myFactoryService){ $scope.rslt = myFactoryService.getData(); }); 

在HTML HTML中,你可以像这样检查

 <div ng-controller='FirstController'> </div> <div ng-controller='SecondController'> {{rslt}} </div> 

关于原始代码 – 看来你想在范围之间共享数据。 要在$ scope之间共享数据或状态,文档build议使用服务:

  • 运行跨控制器共享的无状态或有状态代码 – 使用angular度服务。
  • 实例化或pipe理其他组件的生命周期(例如,创build服务实例)。

参考:Angular Docs链接在这里

我已经开始使用Postal.js作为控制器之间的消息总线。

作为一个消息总线,AMQP风格的绑定,邮政可以集成w / iFrames和Web套接字的方式,还有更多的好处。

我用一个装饰器来获得在$scope.$bus上设置的Postal $scope.$bus

 angular.module('MyApp') .config(function ($provide) { $provide.decorator('$rootScope', ['$delegate', function ($delegate) { Object.defineProperty($delegate.constructor.prototype, '$bus', { get: function() { var self = this; return { subscribe: function() { var sub = postal.subscribe.apply(postal, arguments); self.$on('$destroy', function() { sub.unsubscribe(); }); }, channel: postal.channel, publish: postal.publish }; }, enumerable: false }); return $delegate; }]); }); 

这里有一个关于主题的博客文章的链接…
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/

这就是我如何使用Factory / Services和简单dependency injection(DI)来完成的 。

 myApp = angular.module('myApp', []) # PeopleService holds the "data". angular.module('myApp').factory 'PeopleService', ()-> [ {name: "Jack"} ] # Controller where PeopleService is injected angular.module('myApp').controller 'PersonFormCtrl', ['$scope','PeopleService', ($scope, PeopleService)-> $scope.people = PeopleService $scope.person = {} $scope.add = (person)-> # Simply push some data to service PeopleService.push angular.copy(person) ] # ... and again consume it in another controller somewhere... angular.module('myApp').controller 'PeopleListCtrl', ['$scope','PeopleService', ($scope, PeopleService)-> $scope.people = PeopleService ] 

我喜欢用$rootscope.emit来实现相互通信的方式。 我build议清洁和性能有效的解决scheme,而不会污染全球空间。

 module.factory("eventBus",function (){ var obj = {}; obj.handlers = {}; obj.registerEvent = function (eventName,handler){ if(typeof this.handlers[eventName] == 'undefined'){ this.handlers[eventName] = []; } this.handlers[eventName].push(handler); } obj.fireEvent = function (eventName,objData){ if(this.handlers[eventName]){ for(var i=0;i<this.handlers[eventName].length;i++){ this.handlers[eventName][i](objData); } } } return obj; }) //Usage: //In controller 1 write: eventBus.registerEvent('fakeEvent',handler) function handler(data){ alert(data); } //In controller 2 write: eventBus.fireEvent('fakeEvent','fakeData'); 

这是快速和肮脏的方式。

 // Add $injector as a parameter for your controller function myAngularController($scope,$injector){ $scope.sendorders = function(){ // now you can use $injector to get the // handle of $rootScope and broadcast to all $injector.get('$rootScope').$broadcast('sinkallships'); }; } 

以下是在任何兄弟控制器中添加的示例函数:

 $scope.$on('sinkallships', function() { alert('Sink that ship!'); }); 

当然这里是你的HTML:

 <button ngclick="sendorders()">Sink Enemy Ships</button> 

你可以在模块的任何地方访问这个hello函数

控制器之一

  $scope.save = function() { $scope.hello(); } 

第二控制器

  $rootScope.hello = function() { console.log('hello'); } 

更多信息在这里

我将创build一个服务和使用通知。

  1. 在通知服务中创build一个方法
  2. 创build一个通用的方法在通知服务中广播通知。
  3. 从源控制器调用notificationService.Method。 如果需要,我也传递相应的对象来坚持。
  4. 在该方法中,我将数据保存在通知服务中,并调用通用通知方法。
  5. 在目标控制器中,我监听($ scope.on)广播事件并从通知服务访问数据。

在任何时候,Notification Service都是单例,它应该能够提供持久的数据。

希望这可以帮助

您可以使用AngularJS内置服务$rootScope并在两个控制器中注入此服务。 然后,您可以监听在$ rootScope对象上触发的事件。

$ rootScope提供了两个事件调度器,分别叫$emit and $broadcast ,负责调度事件(可以是自定义事件),使用$rootScope.$on函数添加事件监听器。

您应该使用服务,因为$rootscope是从整个应用程序访问的,并且增加了负载,或者如果您的数据不多,则使用rootparams。

 function mySrvc() { var callback = function() { } return { onSaveClick: function(fn) { callback = fn; }, fireSaveClick: function(data) { callback(data); } } } function controllerA($scope, mySrvc) { mySrvc.onSaveClick(function(data) { console.log(data) }) } function controllerB($scope, mySrvc) { mySrvc.fireSaveClick(data); } 

你可以通过使用$ emit和$ broadcast的angular度事件来实现。 据我们所知,这是最好的,有效的和有效的方法。

首先我们从一个控制器调用一个函数。

 var myApp = angular.module('sample', []); myApp.controller('firstCtrl', function($scope) { $scope.sum = function() { $scope.$emit('sumTwoNumber', [1, 2]); }; }); myApp.controller('secondCtrl', function($scope) { $scope.$on('sumTwoNumber', function(e, data) { var sum = 0; for (var a = 0; a < data.length; a++) { sum = sum + data[a]; } console.log('event working', sum); }); }); 

您也可以使用$ rootScope来代替$ scope。 相应使用你的控制器。

从1.5开始,它是基于组件的开发重点。 推荐的组件交互方式是通过使用'require'属性和属性绑定(input/输出)。

一个组件将需要另一个组件(例如根组件)并获得对其控制器的引用:

 angular.module('app').component('book', { bindings: {}, require: {api: '^app'}, template: 'Product page of the book: ES6 - The Essentials', controller: controller }); 

然后可以使用子组件中的根组件的方法:

 $ctrl.api.addWatchedBook('ES6 - The Essentials'); 

这是根组件控制器function:

 function addWatchedBook(bookName){ booksWatched.push(bookName); } 

这是一个完整的架构概述: 组件通信