Каков правильный способ связи между контроллерами в 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.
однако, есть один gotcha при использовании его из контроллеров. Если вы напрямую связываетесь с $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);
}
]);
I скажем, это не совсем угловая конкретная вещь, поскольку она применяется и к другим реализациям EventBus, что вам нужно очистить ресурсы.
, вы can сделать вашу жизнь проще для тех случаях. Например, вы можете обезьяна патч$rootScope
и дайте ему $onRootScope
который подписывается на события, испускаемые на $rootScope
но также непосредственно очищает обработчик, когда локальный $scope
разрушается.
самый чистый способ, чтобы обезьяна патч $rootScope
обеспечить такие $onRootScope
метод будет через декоратор (блок запуска, вероятно, сделает это просто отлично, но pssst, не говорите никому)
чтобы убедиться, что $onRootScope
свойство не появляется неожиданно при перечислении над $scope
мы используем:Object.defineProperty()
и set enumerable
to 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, который показывает, сколько воздействия perf $broadcast
приносит в таблицу в приличном сценарии всего 100 $scope
s.
на лучшие ответы здесь была работа вокруг угловой проблемы, которая больше не существует (по крайней мере, в версиях >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
, будет автоматически уничтожена при удалении связанного контроллера.
используя $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 в качестве службы. Затем введите 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, способ, которым postal может интегрировать 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;
}]);
});
вот ссылка на сообщение в блоге на topic...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/
вот как я это делаю с Фабрики / Услуги и просто инъекция зависимостей (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>
Я создам сервис и использую уведомление.
- создайте метод в Службе уведомлений
- создайте общий метод широковещательного уведомления в Службе уведомлений.
- из исходного контроллера вызовите notificationService.Метод. Я также передать соответствующий объект для сохранения, если это необходимо.
- в рамках метода я сохраняю данные в службе уведомлений и вызываю общий метод notify.
- в пункт назначения контроллер I listen ($scope.on) для широковещательного события и доступа к данным из службы уведомлений.
поскольку в любой момент служба уведомлений является одноэлементной, она должна иметь возможность предоставлять сохраненные данные.
надеюсь, что это помогает
вы можете использовать AngularJS встроенный сервис $rootScope
и введите эту услугу в оба ваших контроллера.
Затем вы можете прослушивать события, которые запускаются на объекте $rootScope.
$rootScope предоставляет два диспетчера событий под названием $emit and $broadcast
которые отвечают за диспетчеризацию событий(может быть пользовательские события) и использовать $rootScope.$on
функция для добавления прослушивателя событий.
вы должны использовать сервис, потому что $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);
}
вы можете сделать это, используя угловые события, которые $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. Используйте свой контроллер.
начиная угловой 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);
}
вот полный обзор архитектуры:Компонент Коммуникации