474

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

我目前正在使用一种可怕的软糖,涉及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);
    }
}
4

19 回答 19

460

编辑:此答案中解决的问题已在 angular.js版本 1.2.7中得到解决。$broadcast现在避免在未注册的范围上冒泡,并且运行速度与 $emit 一样快。 $broadcast 性能与 angular 1.2.16 的 $emit 相同

所以,现在你可以:

  • $broadcast从使用$rootScope
  • 从需要了解事件$on 的当地人$scope那里收听

原答案如下

我强烈建议不要使用$rootScope.$broadcast+$scope.$on而是使用$rootScope.$emit+ $rootScope.$on。前者可能会导致@numan 提出的严重性能问题。那是因为事件将在所有范围内冒泡。

但是,后者(使用$rootScope.$emit+ $rootScope.$on不受此影响,因此可以用作快速通信通道!

从角度文档$emit

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

由于没有上述范围$rootScope,因此不会发生冒泡。$rootScope.$emit()将/$rootScope.$on()用作 EventBus是完全安全的。

但是,在 Controller 中使用它时有一个问题。如果您直接从控制器中绑定到,则当您的本地被破坏$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

猴子补丁$rootScope提供这种$onRootScope方法的最干净的方法是通过装饰器(运行块可能也可以做到这一点,但是pssst,不要告诉任何人)

为了确保$onRootScope在枚举时属性不会出现意外,$scope我们使用Object.defineProperty()并设置enumerablefalse. 请记住,您可能需要 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,它显示了$broadcast在只有 100 个的体面场景中性能影响有多大$scope

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

jsperf 结果

于 2013-10-21T15:00:52.473 回答
108

正如@zumalifeguard 提到的那样,这里的最佳答案是解决不再存在的Angular问题(至少在版本> 1.2.16和“可能更早”中)。但是我在没有实际解决方案的情况下阅读所有这些答案。

在我看来,现在的答案应该是

  • $broadcast从使用$rootScope
  • 从需要了解事件$on 的当地人$scope那里收听

所以要发布

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

}]);

冲锋衣

如果在 local 上注册监听器,当关联的控制器被移除时,$scope自动销毁。$destroy

于 2014-09-26T13:15:14.607 回答
54

使用$rootScope.$broadcast和 $scope.$on 进行 PubSub 通信。

另外,请参阅这篇文章:AngularJS – 在控制器之间进行通信

于 2012-06-28T21:17:14.973 回答
42

由于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
        });
    }
    
于 2014-02-17T03:30:50.007 回答
20

GridLinked发布了一个PubSub解决方案,该解决方案似乎设计得很好。可以在此处找到该服务。

还有他们的服务图:

消息服务

于 2012-07-10T20:47:26.397 回答
15

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

我建议使用服务。这是我最近在我的一个项目中实现它的方式 - https://gist.github.com/3384419

基本思想 - 将 pubsub/event bus 注册为服务。然后在您需要订阅或发布事件/主题的任何地方注入该事件总线。

于 2012-08-18T04:30:08.183 回答
14

在服务中使用 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>
于 2014-10-14T15:10:40.583 回答
8

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

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

参考:Angular Docs 链接在这里

于 2013-05-11T07:08:08.030 回答
5

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

作为消息总线,它有很多好处,例如 AMQP 样式绑定、postal 可以集成 w/iFrames 和 Web 套接字的方式,等等。

我使用了一个装饰器来设置 Postal $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/

于 2014-05-07T22:55:31.157 回答
3

这就是我使用Factory / Services和简单依赖注入 (DI)的方式。

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
]
于 2014-07-31T09:37:44.743 回答
3

我喜欢如何$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');
于 2015-10-04T08:59:19.083 回答
2

这是快速而肮脏的方法。

// 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>
于 2013-07-30T19:51:35.570 回答
1

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

}

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

于 2016-11-24T10:15:57.513 回答
0

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

控制器一

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

第二控制器

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

更多信息在这里

于 2014-03-18T09:56:54.803 回答
0

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

  1. 在通知服务中创建一个方法
  2. 在 Notification Service 中创建一个用于广播通知的通用方法。
  3. 从源控制器调用 notificationService.Method。如果需要,我还会传递相应的对象以持久化。
  4. 在该方法中,我将数据保存在通知服务中并调用通用通知方法。
  5. 在目标控制器中,我侦听 ($scope.on) 广播事件并从通知服务访问数据。

在任何时候通知服务都是单例的,它应该能够提供持久的数据。

希望这可以帮助

于 2014-06-06T10:41:23.460 回答
0

您应该使用 Service ,因为$rootscope是从整个 Application 访问,它会增加负载,或者如果您的数据不多,您可以使用 rootparams。

于 2014-12-28T22:00:08.230 回答
0

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

$rootScope 提供了两个事件调度$emit and $broadcast器,分别负责调度事件(可能是自定义事件)和使用$rootScope.$on函数添加事件监听器。

于 2014-12-30T09:19:28.863 回答
0
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);
}
于 2015-03-12T07:23:35.630 回答
0

您可以通过使用 $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。相应地使用您的控制器。

于 2017-05-25T13:16:41.067 回答