Каков правильный способ связи между контроллерами?
В настоящее время я использую ужасное выдумку с участием 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);
}
}
Изменить. Проблема, затронутая в этом ответе, решена в angular.js версии 1.2.7. $broadcast
теперь позволяет избежать пузырьков по незарегистрированным областям и работает так же быстро, как и $emit.
Итак, теперь вы можете:
$broadcast
из $rootScope
$on
из локального $scope
, который должен знать о событииОригинальный ответ ниже
Я настоятельно рекомендую не использовать $rootScope.$broadcast
+ $scope.$on
, а скорее $rootScope.$emit
+ $rootScope.$on
. Первый может вызвать серьезные проблемы с производительностью, вызванные @numan. Это связано с тем, что событие будет проходить через области все.
Однако последний (используя $rootScope.$emit
+ $rootScope.$on
) страдает от не и поэтому может быть использован как быстрый канал связи!
Из angular документации $emit
:
Отправляет имя события вверх по иерархии областей, уведомляя зарегистрированный
Так как нет области выше $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, никому не расскажет)
Чтобы убедиться, что свойство $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
.
Btw, я пытаюсь убедить команду angular решить проблему в ядре angular. Здесь идет обсуждение: https://github.com/angular/angular.js/issues/4574
Вот jsperf, который показывает, сколько из перфоманса $broadcast
приводит к таблице в достойном сценарии всего за 100 $scope
.
http://jsperf.com/rootscope-emit-vs-rootscope-broadcast
top answer здесь была проблема с проблемой 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
Controller As
Если вы зарегистрируете прослушиватель на локальном $scope
, он будет автоматически уничтожен самим $destroy
при удалении соответствующего контроллера.
controllerAs
? Я мог использовать $rootScope
в подписчике для прослушивания события, но мне было просто любопытно, был ли другой шаблон.
$scope
. Джон Папа пишет, что события являются одним «исключением» из его обычного правила недопущения $scope
«вне» его контроллеров (я использую кавычки, потому что, поскольку он упоминает, что Controller As
все еще имеет $scope
, он просто находится под капотом).
Используя $rootScope. $broadcast и $scope. $on для связи PubSub.
Также см. этот пост: AngularJS - Связь между контроллерами
$rootScope
и $watch
. Не уверен, что это улучшение.
Поскольку у 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 в качестве сервиса. Затем введите этот 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 и веб-сокеты и многое другое.
Я использовал декоратор, чтобы настроить 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/
Мне понравилось, как $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');
Вот как я это делаю с 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
]
Вот быстрый и грязный способ.
// 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>
$rootScope
?
Вы можете сделать это, используя события 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. Используйте ваш контроллер соответственно.
Запуск 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);
}
Вот полный обзор архитектуры: Компонентные коммуникации
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
для добавления прослушивателя событий.
Вы должны использовать Сервис, потому что $rootscope
- это доступ из всего приложения, и он увеличивает нагрузку, или вы используете корневые патчи, если ваши данные не больше.
Я создам службу и использую уведомление.
Как и в любой точке службы Notification Service, она должна обеспечивать постоянную передачу данных.
Надеюсь, что это поможет
Вы можете получить доступ к этой функции hello в любом месте модуля
Контроллер один
$scope.save = function() {
$scope.hello();
}
второй контроллер
$rootScope.hello = function() {
console.log('hello');
}