Как правильно общаться между контроллерами в 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);
}
}
19 ответов
Редактировать: проблема, затронутая в этом ответе, была решена в angular.js версии 1.2.7. $broadcast
теперь избегает пузырей над незарегистрированными областями и работает так же быстро, как $ 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.
Однако при использовании изнутри контроллеров есть одна ошибка. Если вы напрямую связаны с $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, не говорите никому)
Чтобы убедиться, что $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
,
Кстати, я пытаюсь убедить команду разработчиков Angular решить проблему в рамках ядра Angular. Здесь идет обсуждение: https://github.com/angular/angular.js/issues/4574
Вот jsperf, который показывает, насколько удачное влияние $broadcast
приносит к столу в приличном сценарии всего с 100 $scope
"S.
Верхний ответ здесь - это решение проблемы 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');
}]);
И подписаться
// 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
сам, когда связанный контроллер удален.
Использование $rootScope.$ Broadcast и $ scope. $ On для связи с PubSub.
Также смотрите этот пост: AngularJS - Связь между контроллерами
Поскольку у 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 }); }
GridLinked опубликовал решение PubSub, которое, кажется, разработано довольно хорошо. Услугу можно найти здесь.
Также схема их обслуживания:
На самом деле использование emit и broadcast неэффективно, потому что событие всплывает вверх и вниз по иерархии областей действия, что может легко перерасти в снижение производительности для сложного приложения.
Я бы предложил воспользоваться услугой. Вот как я недавно реализовал это в одном из моих проектов - https://gist.github.com/3384419.
Основная идея - зарегистрировать pubsub / event bus как сервис. Затем внедрите этот eventbus туда, где вам нужно подписаться или публиковать события / темы.
Используя методы 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>
Что касается исходного кода - кажется, вы хотите обмениваться данными между областями. Чтобы поделиться данными или состоянием между $ scope, документы предлагают использовать сервис:
- Для запуска кода без сохранения состояния или с сохранением состояния, общего для контроллеров - используйте вместо этого угловые сервисы.
- Для создания экземпляра или управления жизненным циклом других компонентов (например, для создания экземпляров службы).
Я фактически начал использовать Postal.js в качестве шины сообщений между контроллерами.
В этом есть множество преимуществ, таких как шина сообщений, такая как привязки в стиле AMQP, способ, которым почтовые службы могут интегрировать w / iFrames и веб-сокеты, и многое другое.
Я использовал декоратор, чтобы настроить Почту на $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/
Вот как я это делаю с 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
]
Мне понравилось как $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');
Вот быстрый и грязный способ.
// 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 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);
}
Вот полный обзор архитектуры: связь компонентов
Вы можете сделать это с помощью угловых событий $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. Используйте свой контроллер соответственно.
Вы должны использовать Сервис, потому что $rootscope
Это доступ из всего приложения, и это увеличивает нагрузку, или вы можете использовать rootparams, если ваши данные не больше.
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);
}
Вы можете использовать встроенный сервис AngularJS $rootScope
и внедрите этот сервис в оба ваших контроллера. Затем вы можете прослушивать события, которые запускаются на объекте $rootScope.
$rootScope предоставляет два диспетчера событий с именем $emit and $broadcast
которые отвечают за диспетчеризацию событий (могут быть пользовательские события) и использование $rootScope.$on
функция для добавления прослушивателя событий.
Я создам сервис и буду использовать уведомления.
- Создать метод в службе уведомлений
- Создайте общий метод для широковещательного уведомления в службе уведомлений.
- Из контроллера источника вызовите уведомление Service.Method. Я также передаю соответствующий объект для сохранения в случае необходимости.
- В рамках метода я сохраняю данные в службе уведомлений и вызываю общий метод уведомления.
- В контроллере назначения я прослушиваю ($scope.on) широковещательное событие и получаю доступ к данным из службы уведомлений.
Поскольку в любой момент служба уведомлений является одноуровневой, она должна иметь возможность предоставлять постоянные данные.
Надеюсь это поможет
Вы можете получить доступ к этой функции привет в любом месте модуля
Контроллер один
$scope.save = function() {
$scope.hello();
}
второй контроллер
$rootScope.hello = function() {
console.log('hello');
}