在AngularJS中,控制器之间通信的正确方式是什么?
在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问题的方法,但在版本 >1.2.16和“可能更早”的情况下已经不再存在,正如@zumalifeguard所提到的。但是,我仍然在阅读所有这些答案而没有实际解决方案。
现在,答案似乎应该是:
- 使用
$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
- 常规的$scope语法(如上所示)
- 新的
Controller As
语法
如果在本地$scope
中注册侦听器,则当关联的控制器被删除时,它将被$destroy
自动销毁。
修改: 这个回答中提到的问题已经在 angular.js 版本1.2.7中解决。 现在$broadcast
避免了传播到未注册范围并且一样快,就像$emit
一样。
现在您可以:
- 使用
$rootScope
的$broadcast
- 使用
$on
从需要知道事件的本地$scope
进行侦听
以下为原始回答
我强烈建议不要使用$rootScope.$broadcast
+ $scope.$on
,而是使用$rootScope.$emit
+ $rootScope.$on
。前者可能会导致性能问题,就像@numan提出的那样。原因是事件会通过所有作用域进行传播。
然而,使用$rootScope.$emit
+ $rootScope.$on
的后者不会受到这个问题的影响,因此可以用作一个快速的通信通道!
根据$emit
的angular文档:
通过作用域层次结构向上传递事件名称,通知注册的相关方
由于$rootScope
上方没有作用域,因此没有冒泡发生。使用$rootScope.$emit()
/$rootScope.$on()
作为EventBus是完全安全的。
不过,在控制器中使用它时,有一个要注意的地方。如果你在控制器中直接绑定到$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); } ]);
我会说,这不是一个特定于Angular的事情,因为它也适用于其他事件总线实现,您必须清理资源。
但是,您可以使这些情况更加容易。例如,您可以配置$rootScope
并为其提供一个$onRootScope
,该方法订阅在$rootScope
上发出的事件,但也直接在本地$scope
被销毁时清理处理程序。
将$rootScope
进行最干净的猴子补丁,以提供这种$onRootScope
方法的方法是通过装饰器(一个运行块也可能做得很好,但嘘,别告诉任何人)
为了确保在枚举$scope
时不会意外出现$onRootScope
属性,我们使用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'); }); } ]);
所以,最终结果是我强烈建议你使用$rootScope.$emit
+ $scope.$onRootScope
。
顺便说一句,我正在试图说服Angular团队解决Angular核心中的问题。这里正在进行讨论:https://github.com/angular/angular.js/issues/4574
这里是一个jsperf,展示了在一个普通场景中有100个$scope
的情况下,$broadcast
带来了多大的性能影响。