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

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

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 从需要了解事件的本地$作用域使用$on进行监听


原始答案如下

我强烈建议不要使用$rootScope。$broadcast + $scope。$on而不是$rootScope。发出+ rootScope。美元美元。前者会导致严重的性能问题,正如@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特有的事情,因为它也适用于其他EventBus实现,你必须清理资源。

但是,对于这些情况,您可以让您的生活变得更轻松。例如,你可以给$rootScope打补丁,并给它一个$onRootScope,订阅在$rootScope上发出的事件,但也可以在本地$scope被破坏时直接清理处理程序。

猴子修补$rootScope以提供这样的$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');
            });
        }
    ]);

因此,作为这一切的最终结果,我强烈建议您使用$rootScope。$emit + $scope.$onRootScope。

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

下面是一个jsperf,它展示了$broadcast在一个只有100个$scope的合适场景中所带来的性能影响。

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

其他回答

这是一个快速而肮脏的方法。

// 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>

在服务中使用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中,你可以这样检查

<div ng-controller='FirstController'>  
</div>

<div ng-controller='SecondController'>
    {{rslt}}
</div>
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);
}

关于原始代码——看起来您想要在作用域之间共享数据。要在$scope之间共享数据或状态,文档建议使用服务:

要运行跨控制器共享的无状态或有状态代码-使用 而是Angular服务。 实例化或管理的生命周期 其他组件(例如,用于创建服务实例)。

参考:Angular Docs链接在这里

实际上,我已经开始使用post .js作为控制器之间的消息总线。

它作为消息总线有很多好处,比如AMQP风格的绑定,邮政可以集成w/ iFrames和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/