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

fad*_*bee 470 scope 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*oph 454

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

所以,现在你可以:

  • 使用$broadcast$rootScope
  • $on $scope需要了解事件的本地人那里听取意见

原文如下

我强烈建议不要使用$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$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);
        }
    ]);
Run Code Online (Sandbox Code Playgroud)

我会说,这不是一个特定角度的东西,因为它也适用于其他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;
        }]);
    }]);
Run Code Online (Sandbox Code Playgroud)

有了这种方法,上面的控制器代码可以简化为:

angular
    .module('MyApp')
    .controller('MyController', ['$scope', function MyController($scope) {

            $scope.$onRootScope('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });
        }
    ]);
Run Code Online (Sandbox Code Playgroud)

所以作为所有这些的最终结果,我强烈建议你使用$rootScope.$emit+ $scope.$onRootScope.

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

这是一个jsperf,它显示了$broadcast在一个体面的场景中,只有100 $scope秒的性能影响会给桌面带来多少影响.

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

jsperf结果

  • 这是解决问题的一个非常聪明的解决方案,但不再需要它.最新版本的Angular(1.2.16),可能更早,已经解决了这个问题.现在$ broadcast不会因为没有原因访问每个后代控制器.它只会访问那些实际上正在听取此事件的人.我更新了上面引用的jsperf,以证明问题现已解决:http://jsperf.com/rootscope-emit-vs-rootscope-broadcast/27 (59认同)

pos*_*est 106

这里的最佳答案是解决了一个不再存在的Angular问题(至少在版本> 1.2.16和"可能更早"),正如@zumalifeguard所提到的那样.但是,如果没有实际的解决方案,我会离开阅读所有这些答案.

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

  • 使用$broadcast$rootScope
  • $on $scope需要了解事件的本地人那里听取意见

所以要发布

// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {

  $rootScope.$broadcast('topic', 'message');

}]);
Run Code Online (Sandbox Code Playgroud)

并订阅

// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {

  $scope.$on('topic', function (event, arg) { 
    $scope.receiver = 'got your ' + arg;
  });

}]);
Run Code Online (Sandbox Code Playgroud)

Plunkers

如果您在本地注册的监听器$scope,它会自动销毁$destroy本身时,相关的控制器被删除.

  • @edhedges我想你可以明确地注入`$ scope`.[约翰帕帕写道](http://www.johnpapa.net/do-you-like-your-angular-controllers-with-or-without-sugar/)关于事件是他通常的保持规则的"例外" `$ scope`"out"他的控制器(我使用引号,因为他提到`Controller As`仍然有'$ scope`,它只是在引擎盖下). (3认同)
  • @dsdsdsdsd,服务/工厂/提供商将永远留在这里.在Angular应用程序中总是只有一个(单身).另一方面,控制器与功能相关联:组件/指令/ ng-controller,可以重复(如类中的对象),并根据需要来去.为什么你想要一个控件及其控制器在你不再需要它时保持存在?这就是内存泄漏的定义. (3认同)
  • @edhedges我根据请求使用`controller as`语法替换我的答案.我希望这就是你的意思. (2认同)

Ren*_*des 53

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

另请参阅此文章:AngularJS - 在控制器之间进行通信

  • 该视频只是重新发明了`$ rootScope`和`$ watch`.不确定是否有任何改进. (3认同)

小智 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;
    }]);
});
Run Code Online (Sandbox Code Playgroud)

并在控制器中使用它,如下所示:

  • 范围被破坏时自动取消订阅+1. (7认同)
  • 我喜欢这个解决方案.我做了2次更改:(1)允许用户将'data'传递给emit消息(2)使'scope'的传递可选,这样就可以在单例服务和控制器中使用.您可以在此处看到这些更改:https://gist.github.com/turtlemonvh/10686980/038e8b023f32b98325363513bf2a7245470eaf80 (6认同)

Rya*_*her 20

GridLinked发布了一个PubSub解决方案,似乎设计得非常好.这里可以找到服务.

还有他们的服务图:

消息服务


num*_*ati 15

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

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

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

  • 当不再需要控制器时,您如何自动取消订阅?如果你不这样做,由于关闭,控制器将永远不会从内存中删除,你仍然会感知它的消息.为避免这种情况,您需要手动删除.在此上使用$不会发生. (7认同)
  • 我只是把它留在这里,因为之前没有人说过.使用rootScope作为EventBus**不是低效的`$ rootScope.$ emit()`只有气泡向上.但是,由于`$ rootScope`上面没有范围,因此没有什么可担心的.因此,如果您只是使用`$ rootScope.$ emit()`和`$ rootScope.$ on on()`,您将拥有一个快速系统范围的EventBus. (6认同)

小智 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();
});
Run Code Online (Sandbox Code Playgroud)

在HTML HTML中,您可以像这样检查

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

<div ng-controller='SecondController'>
    {{rslt}}
</div>
Run Code Online (Sandbox Code Playgroud)


pkb*_*ron 8

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

  • 运行跨控制器共享的无状态或有状态代码 - 改为使用角度服务.
  • 实例化或管理其他组件的生命周期(例如,创建服务实例).

参考:Angular Docs链接在这里


jcr*_*898 5

我实际上已经开始使用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;
    }]);
});
Run Code Online (Sandbox Code Playgroud)

这是关于该主题的博客文章的链接...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/