提问人:fadedbee 提问时间:6/29/2012 最后编辑:Shashank Agrawalfadedbee 更新时间:6/22/2017 访问量:217074
在 AngularJS 中控制器之间进行通信的正确方式是什么?
What's the correct way to communicate between controllers in 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);
}
}
答:
使用 $rootScope.$broadcast 和 $scope.$on 进行 PubSub 通信。
另外,请参阅这篇文章: AngularJS – 控制器之间的通信
评论
$rootScope
$watch
GridLinked 发布了一个 PubSub 解决方案,该解决方案似乎设计得很好。可以在此处找到该服务。
还有他们的服务图:
实际上,使用 emit 和 broadcast 效率低下,因为事件在作用域层次结构中上下冒泡,这很容易降低到复杂应用程序的性能瓶颈。
我建议使用服务。以下是我最近在我的一个项目中实现它的方式 - https://gist.github.com/3384419。
基本思想 - 将 pubsub/event 总线注册为服务。然后将该事件总线注入到需要订阅或发布事件/主题的任何位置。
评论
$rootScope.$emit()
$rootScope
$rootScope.$emit()
$rootScope.$on()
$rootScope.$on()
$rootScope
关于原始代码 - 您似乎希望在作用域之间共享数据。要在$scope之间共享数据或状态,文档建议使用服务:
- 运行在控制器之间共享的无状态或有状态代码 — 使用 相反,angular 服务。
- 实例化或管理 其他组件(例如,用于创建服务实例)。
这是快速而肮脏的方法。
// 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>
评论
$rootScope
编辑:此答案中解决的问题已在 angular.js 版本 1.2.7 中得到解决。 现在避免了在未注册的范围上冒泡,并且运行速度与$emit一样快。$broadcast
因此,现在您可以:
- 从
$broadcast
$rootScope
- 使用需要了解事件的本地
$scope
收听$on
原始答案如下
我强烈建议不要使用 +,而是使用 + 。正如@numan所提出的,前者可能会导致严重的性能问题。这是因为该事件将在所有范围内冒泡。$rootScope.$broadcast
$scope.$on
$rootScope.$emit
$rootScope.$on
但是,后者(使用 + )不受此影响,因此可以用作快速通信渠道!$rootScope.$emit
$rootScope.$on
从 angular 文档:$emit
通过作用域层次结构向上调度事件名称,通知已注册的事件
由于上面没有范围,所以不会发生冒泡。使用/作为 EventBus 是完全安全的。$rootScope
$rootScope.$emit()
$rootScope.$on()
但是,在控制器中使用它时有一个问题。如果直接从控制器内绑定到控制器,则当本地绑定被销毁时,必须自行清理绑定。这是因为控制器(与服务相比)可以在应用程序的生命周期内多次实例化,这将导致绑定相加,最终到处造成内存泄漏:)$rootScope.$on()
$scope
要注销,只需侦听 的事件,然后调用 返回的函数。$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);
}
]);
我想说的是,这并不是一个特定于角度的事情,因为它也适用于其他 EventBus 实现,您必须清理资源。
但是,对于这些情况,您可以让您的生活更轻松。例如,你可以给它一个猴子补丁,并给它一个订阅在 上发出的事件,但也会在本地被破坏时直接清理处理程序。$rootScope
$onRootScope
$rootScope
$scope
提供这种方法的最干净的方法是通过装饰器(运行块也可能做得很好,但 pssst,不要告诉任何人)$rootScope
$onRootScope
为了确保该属性在枚举时不会意外显示,我们使用 并设置为 。请记住,您可能需要一个 ES5 垫片。$onRootScope
$scope
Object.defineProperty()
enumerable
false
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');
});
}
]);
因此,作为所有这些的最终结果,我强烈建议您使用 + .$rootScope.$emit
$scope.$onRootScope
顺便说一句,我试图说服 angular 团队解决 angular 核心中的问题。这里正在进行讨论:https://github.com/angular/angular.js/issues/4574
这是一个 jsperf,它显示了在只有 100 个的体面场景中,性能影响会带来多大的影响。$broadcast
$scope
http://jsperf.com/rootscope-emit-vs-rootscope-broadcast
评论
由于 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 }); }
评论
您可以在模块中的任何位置访问此 hello 函数
控制器一
$scope.save = function() {
$scope.hello();
}
第二个控制器
$rootScope.hello = function() {
console.log('hello');
}
评论
实际上,我已经开始使用Postal.js作为控制器之间的消息总线。
作为消息总线,它有很多好处,例如 AMQP 样式绑定、邮政可以集成 iFrame 和 Web 套接字的方式,以及更多的东西。
我使用装饰器来设置邮政......$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/
我将创建一个服务并使用通知。
- 在 Notification Service 中创建方法
- 创建一个通用方法以在 Notification Service 中广播通知。
- 从源控制器调用 notificationService.Method。如果需要,我还传递相应的对象以持久化。
- 在该方法中,我将数据保存在通知服务中并调用通用通知方法。
- 在目标控制器中,我侦听 ($scope.on) 广播事件并从通知服务访问数据。
由于在任何时候,通知服务都是单例的,因此它应该能够提供持久的数据。
希望这会有所帮助
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
]
评论
这里的最高答案是解决 Angular 问题,该问题不再存在(至少在版本 >1.2.16 和“可能更早”中),正如@zumalifeguard提到的。但是我只能阅读所有这些答案,而没有实际的解决方案。
在我看来,现在的答案应该是
- 从
$broadcast
$rootScope
- 使用需要了解事件的本地
$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
如果在本地上注册侦听器,则当删除关联的控制器时,$destroy
本身将自动销毁该侦听器。$scope
评论
controllerAs
$rootScope
$scope
$scope
Controller As
$scope
controller as
在服务中使用 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>
评论
您应该使用服务,因为它是从整个应用程序访问的,并且会增加负载,或者如果您的数据不多,则可以使用rootparams。$rootscope
您可以使用 AngularJS 内置服务并将此服务注入到您的两个控制器中。
然后,您可以侦听在$rootScope对象上触发的事件。$rootScope
$rootScope提供了两个事件调度器,分别负责调度事件(可能是自定义事件)并使用函数添加事件监听器。$emit and $broadcast
$rootScope.$on
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);
}
我喜欢如何实现相互交流的方式。我建议在不污染全球空间的情况下使用清洁且性能有效的解决方案。$rootscope.emit
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');
评论
从 angular 1.5 开始,它是基于组件的开发重点。组件交互的推荐方式是使用“require”属性和属性绑定(输入/输出)。
一个组件需要另一个组件(例如根组件)并获取对其控制器的引用:
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 addWatchedBook(bookName){
booksWatched.push(bookName);
}
下面是一个完整的架构概述: 组件通信
您可以使用 $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。相应地使用您的控制器。
评论