提问



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


我目前正在使用涉及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一样快。
[74]


所以,现在你可以:



  • 使用$rootScope
  • 中的$broadcast
  • 使用需要了解该事件的本地$scope $on 收听






下面的原始答案


我强烈建议不要使用$rootScope.$broadcast + $scope.$on而是$rootScope.$emit + $rootScope.$on。前者会导致严重的性能问题,如@numan提出的那样。这是因为该事件将通过所有范围向下消失。


然而,后者(使用$rootScope.$emit + $rootScope.$on)会受此影响,因此可以用作快速通信渠道!


$emit的角度文件中:



  通过范围层次结构向上调度事件名称,通知已注册的事件



由于$rootScope之上没有范围,所以没有发生冒泡。使用$rootScope.$emit()/$rootScope.$on()作为EventBus是完全安全的。


但是,在Controllers中使用它时有一个问题。如果从控制器中直接绑定到$rootScope.$on(),当本地$scope被破坏时,你将不得不自己清理绑定。这是因为控制器(与服务相反)可以实例化多个在应用程序的生命周期中的时间,这将导致绑定总结最终在整个地方创建内存泄漏:)


要取消注册,只需听$scope [[s $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被破坏时直接清理处理程序。


修补$rootScope以提供这种$onRootScope方法的最简洁方法是通过一个装饰器(一个运行块可能会做得很好但是pssst,不要告诉任何人)


为了确保枚举$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


顺便说一下,我试图说服角度团队在角度核心内解决问题。这里有一个讨论:https://github.com/angular/angular.js/issues/4574 [75]


这是一个jsperf,它显示了在一个体面的场景中,只有100 $scope秒对表格有多大影响$broadcast


http://jsperf.com/rootscope-emit-vs-rootscope-broadcast[76]




其它参考1


这里的最佳答案是解决一个不再存在的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本身将自动销毁该侦听器。 [78] [79]

其它参考2


使用$ rootScope。$ broadcast和$ scope。$ on进行PubSub通信。[81]


另请参阅以下文章:AngularJS - 控制器之间的通信[82]

其它参考3


由于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
        });
    }
    


其它参考4


GridLinked发布了一个PubSub解决方案,该解决方案似乎设计得非常好。这里的服务可以找到。[83] [84] [85]


还有他们的服务图:




其它参考5


实际上使用发射和广播是低效的,因为事件在范围层次结构中上下波动,这很容易降级为复杂应用程序的性能瓶装。


我建议使用服务。以下是我最近在我的一个项目中实现它的方法 - https://gist.github.com/3384419。 [86]


基本思路 - 将pubsub/event bus注册为服务。然后在需要订阅或发布事件/主题的地方注入eventbus。

其它参考6


使用服务中的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>

其它参考7


关于原始代码 - 您似乎希望在范围之间共享数据。要在$ scope范围内共享数据或状态,文档建议使用服务:



  • 运行跨控制器共享的无状态或有状态代码 - 使用
    角度服务而不是。

  • 实例化或管理生命周期
    其他组件(例如,创建服务实例)。



参考:Angular Docs链接在这里[87]

其它参考8


我实际上已经开始使用Postal.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/[88]

其它参考9


这就是我使用工厂/服务和简单​​依赖注入(DI)的方式。[89] [90]


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
]

其它参考10


我喜欢$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');

其它参考11


这是快速而肮脏的方式。


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

其它参考12


您可以在模块中的任何位置访问此hello函数


控制器一


 $scope.save = function() {
    $scope.hello();
  }


第二控制器


  $rootScope.hello = function() {
    console.log('hello');
  }


更多信息[91]

其它参考13


我将创建一个服务并使用通知。



  1. 在通知服务中创建方法

  2. 创建通用方法以在通知服务中广播通知。

  3. 从源控制器调用notificationService.Method。如果需要,我也会传递相应的对象来保持。

  4. 在该方法中,我将数据保留在通知服务中并调用通用通知方法。

  5. 在目标控制器中,我监听($ scope.on)广播事件并从Notification Service访问数据。



在任何时候,Notification Service都是单例,它应该能够提供持久化数据。


希望这可以帮助

其它参考14


您可以使用AngularJS内置服务$rootScope并在两个控制器中注入此服务。
然后,您可以侦听在$ rootScope对象上触发的事件。


$ rootScope提供了两个名为$emit and $broadcast的事件调度程序,它们负责调度事件(可能是自定义事件)并使用$rootScope.$on函数添加事件监听器。

其它参考15


您应该使用服务,因为$rootscope是从整个应用程序访问,它会增加负载,或者如果您的数据不是更多,则使用rootparams。

其它参考16


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

其它参考17


您可以使用$ emit和$ broadcast的角度事件来完成。据我们所知,这是最好,最有效和最有效的方法。


首先,我们从一个控制器调用一个函数。


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。相应地使用您的控制器

其它参考18


从角度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);

}


这是一个完整的架构概述:组件通信[92]