在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);
}
}
216405 次浏览

使用rootScope。广播美元和$scope。$on PubSub通信。

另外,请参阅这篇文章:AngularJS -控制器之间的通信

GridLinked发布了一个PubSub解决方案,看起来设计得很好。可以在在这里中找到服务。

还有他们的服务图表:

短信服务

实际上,使用emit和broadcast效率很低,因为事件会在作用域层次结构中上下冒泡,这很容易导致复杂应用程序的性能瓶颈。

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

基本思想——将发布/事件总线注册为服务。然后在需要订阅或发布事件/主题的地方注入事件总线。

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

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

Ref: Angular Docs link here .

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

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

编辑:在这个回答中解决的问题已经在angular.js 1.2.7版本中解决了。$broadcast现在避免在未注册的作用域冒泡,并且运行速度和$emit一样快。 $broadcast shows are identical to $emit with angular 1.2.16

.

所以,现在你可以:

  • 使用$rootScope中的$broadcast
  • 使用$on 从本地$scope监听,它需要知道事件

原始答案如下

我强烈建议不要使用$rootScope.$broadcast + $scope.$on,而是使用$rootScope.$emit+ $rootScope.$on。前者会导致严重的性能问题,正如@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,不要告诉任何人)

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

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

下面是一个jsperf,它显示了在一个只有100个$scope的合适场景中,__abc0对性能的影响有多大。

http://jsperf.com/rootscope-emit-vs-rootscope-broadcast < a href = " http://jsperf.com/rootscope-emit-vs-rootscope-broadcast " > < / >

jsperf results

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

并像这样在控制器中使用它:

  • < p >控制器1

    function($scope, msgBus) {
    $scope.sendmsg = function() {
    msgBus.emitMsg('somemsg')
    }
    }
    
  • controller 2

    function($scope, msgBus) {
    msgBus.onMsg('somemsg', $scope, function() {
    // your logic
    });
    }
    

你可以在模块的任何地方访问这个hello函数

控制器的一个

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

第二个控制器

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

更多信息在这里

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

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

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

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

在任何情况下,notificationservice都是单例的,它应该能够跨跨提供持久数据。

希望这能有所帮助

这就是我如何用工厂/服务和简单的依赖注入来做的。

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
]

这里的上面的回答是一个绕过Angular问题的方法,正如@zumalifeguard提到的,这个问题已经不存在了(至少在>1.2.16版本和“可能更早”)。但我读了所有这些答案,却没有一个实际的解决方案。

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

  • 使用$rootScope中的$broadcast
  • 使用$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;
});


}]);

Plunkers

  • 常规$scope语法(如上所示)
  • < a href = " http://plnkr.co/edit/PgPBf5UjYOBBYxThNa7j?p=preview" rel="noreferrer">新的Controller As语法 . p=preview" rel="noreferrer">

如果你在本地$scope上注册侦听器,当相关的控制器被移除时,它将是$destroy本身自动销毁

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

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

你可以使用AngularJS内置服务$rootScope并将该服务注入到你的两个控制器中。 然后,您可以监听在$rootScope对象上触发的事件。< / p >

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

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

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

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


}

下面是一个完整的体系结构概述

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