首页 文章

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

提问于
浏览
462

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

我目前正在使用涉及 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);
    }
}

19 回答

  • 451

    Edit :此答案中解决的问题已在angular.js version 1.2.7中得到解决 . $broadcast 现在避免冒泡未注册的范围,并且运行速度与$ emit一样快 .
    $broadcast performances are identical to $emit with angular 1.2.16

    So, now you can:

    • 使用 $broadcast 来自 $rootScope

    • 使用需要了解该事件的 $on from the local $scope 进行监听


    Original Answer Below

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

    但是,后者(使用 $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);
            }
        ]);
    

    我会说,这不是一个特定角度的东西,因为它也适用于其他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 .

    顺便说一下,我在这里进行讨论:https://github.com/angular/angular.js/issues/4574

    这是一个jsperf,它显示了在一个体面的场景中只有100个 $scope 的表现所带来的影响力 .

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

    jsperf results

  • 102

    top answer这是一个解决Angular问题的工作,不再存在(至少版本> 1.2.16和"probably earlier"),正如@zumalifeguard所提到的那样 . 但是,如果没有实际的解决方案,我会离开阅读所有这些答案 .

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

    • 使用 $broadcast 来自 $rootScope

    • 使用 $on from the local $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;
      });
    
    }]);
    

    Plunkers

    如果在本地 $scope 上注册侦听器,则在删除关联的控制器时将为destroyed automatically by $destroy itself .

  • 0

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

    另外,请看这篇文章:AngularJS – Communicating Between Controllers

  • 0

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

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

    还有他们的服务图:

    Messaging Service

  • 3

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

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

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

  • 0

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

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

    • 运行跨控制器共享的无状态或有状态代码 - 改为使用角度服务 .

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

    Ref: Angular Docs link here

  • 3

    我实际上已经开始使用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/

  • 0

    这是我用Factory / Services和简单的dependency injection (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
    ]
    
  • 0

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

    这是快速而肮脏的方式 .

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

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

    控制器一

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

    第二控制器

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

    More info here

  • 14

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

    • 在Notification Service中创建方法

    • 创建在Notification Service中广播通知的通用方法 .

    • 从源控制器调用notificationService.Method . 如果需要,我还传递相应的对象以保持持久性 .

    • 在方法中,我将数据保留在通知服务中并调用泛型通知方法 .

    • 在目标控制器中,我监听($ scope.on)广播事件并从通知服务访问数据 .

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

    希望这可以帮助

  • 0

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

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

  • 5

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

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

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

  • 15

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

    这是一个完整的架构概述:Component Communications

相关问题