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

22 浏览
0 Comments

在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);
    }
}

admin 更改状态以发布 2023年5月21日
0
0 Comments

这里的最佳回答是一个解决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中注册侦听器,则当关联的控制器被删除时,它将被$destroy自动销毁。

0
0 Comments

修改: 这个回答中提到的问题已经在 angular.js 版本1.2.7中解决。 现在$broadcast避免了传播到未注册范围并且一样快,就像$emit一样。
$ broadcast性能与angular 1.2.16相同

现在您可以:

  • 使用$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带来了多大的性能影响。

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

jsperf results

0