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类似。
所以,现在你可以:
- 从
$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效果。
这里最好的答案是从@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一个服务和使用通知。
- 在通知服务中创build一个方法
- 创build一个通用的方法在通知服务中广播通知。
- 从源控制器调用notificationService.Method。 如果需要,我也传递相应的对象来坚持。
- 在该方法中,我将数据保存在通知服务中,并调用通用通知方法。
- 在目标控制器中,我监听($ 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); }
这是一个完整的架构概述: 组件通信