Работа с $scope.$emit и $scope.$on в AngularJS
Как я могу передать свой объект $scope
из одного контроллера в другой, используя методы .$emit
и .$on
?
function firstCtrl($scope) {
$scope.$emit('someEvent', [1,2,3]);
}
function secondCtrl($scope) {
$scope.$on('someEvent', function(mass) { console.log(mass); });
}
Это не работает так, как я думаю, что должно. Как на самом деле работают $emit
и $on
?
5 ответ(ов)
Первым делом стоит отметить, что связь между родительским и дочерним скоупами имеет значение. У вас есть две возможности для эмита события:
$broadcast
— отправляет событие вниз ко всем дочерним скоупам.$emit
— отправляет событие вверх по иерархии скоупов.
Не зная специфики отношений между вашими контроллерами (скоупами), можно выделить несколько вариантов:
Если скоуп
firstCtrl
является родителем скоупаsecondCtrl
, ваш код будет работать, если заменить$emit
на$broadcast
вfirstCtrl
:function firstCtrl($scope) { $scope.$broadcast('someEvent', [1, 2, 3]); } function secondCtrl($scope) { $scope.$on('someEvent', function(event, mass) { console.log(mass); }); }
В случае, если между вашими скоупами нет родительской и дочерней связи, вы можете внедрить
$rootScope
в контроллер и отправлять событие всем дочерним скоупам (включая иsecondCtrl
):function firstCtrl($rootScope) { $rootScope.$broadcast('someEvent', [1, 2, 3]); }
Наконец, когда нужно отправить событие из дочернего контроллера вверх по иерархии, вы можете использовать
$scope.$emit
. Если скоупfirstCtrl
является родителем скоупаsecondCtrl
:function firstCtrl($scope) { $scope.$on('someEvent', function(event, data) { console.log(data); }); } function secondCtrl($scope) { $scope.$emit('someEvent', [1, 2, 3]); }
Таким образом, выберите подходящий для вашей ситуации способ передачи события.
Я бы также предложил четвертый вариант, который является более подходящей альтернативой предложенным вариантам от @zbynour.
Используйте $rootScope.$emit
, а не $rootScope.$broadcast
, независимо от отношения между передающим и принимающим контроллерами. Таким образом, событие останется в наборе $rootScope.$$listeners
, тогда как при использовании $rootScope.$broadcast
событие будет распространяться на все дочерние области видимости, и большинство из них, скорее всего, не будут слушателями этого события. Конечно, в принимающем контроллере вам просто нужно использовать $rootScope.$on
.
Важно помнить о том, чтобы уничтожить слушатели корневого Scope контроллера:
var unbindEventHandler = $rootScope.$on('myEvent', myHandler);
$scope.$on('$destroy', function () {
unbindEventHandler();
});
Обратите внимание, что это поможет избежать утечек памяти, когда контроллер больше не используется.
Вы можете передавать любой объект в иерархии вашего приложения, включая $scope.
Вот быстрое объяснение того, как работают $broadcast и $emit.
Представьте себе следующие узлы, находящиеся в пределах узла 3. Вы используете $broadcast и $emit, когда сталкиваетесь с такой ситуацией.
Замечание: Номера узлов в этом примере произвольны; это могут быть как 1, так и 2, или даже 1,348. Каждый номер просто идентификатор для данного примера. Основная идея заключается в демонстрации вложенности контроллеров и директив Angular.
3
------------
| |
----- ------
1 | 2 |
--- --- --- ---
| | | | | | | |
Рассмотрим это дерево. Как вы ответите на следующие вопросы?
Замечание: Существуют и другие способы решения этих вопросов, но здесь мы обсудим $broadcast и $emit. Также, при чтении текста ниже предполагайте, что каждый номер имеет свой собственный файл (директива, контроллер), например one.js, two.js, three.js.
Как узел 1 общается с узлом 3?
В файле one.js:
scope.$emit('messageOne', someValue(s));
В файле three.js - верхнем узле для всех дочерних узлов, которым нужно взаимодействовать:
scope.$on('messageOne', someValue(s));
Как узел 2 общается с узлом 3?
В файле two.js:
scope.$emit('messageTwo', someValue(s));
В файле three.js - верхнем узле для всех дочерних узлов, которым нужно взаимодействовать:
scope.$on('messageTwo', someValue(s));
Как узел 3 общается с узлом 1 и/или узлом 2?
В файле three.js - верхнем узле для всех дочерних узлов:
scope.$broadcast('messageThree', someValue(s));
В файлах one.js и two.js - в любом файле, где вы хотите получить сообщение или в обоих:
scope.$on('messageThree', someValue(s));
Как узел 2 общается с узлом 1?
В файле two.js:
scope.$emit('messageTwo', someValue(s));
В файле three.js - верхнем узле для всех дочерних узлов:
scope.$on('messageTwo', function(event, data){
scope.$broadcast('messageTwo', data);
});
В файле one.js:
scope.$on('messageTwo', someValue(s));
ОДНАКО
Когда у вас есть все эти вложенные дочерние узлы, которые пытаются взаимодействовать таким образом, вы быстро увидите множество $on, $broadcast и $emit.
Вот что мне нравится делать.
В верхнем РОДИТЕЛЬСКОМ УЗЛЕ (в данном случае 3), который может быть вашим родительским контроллером...
Таким образом, в файле three.js:
scope.$on('pushChangesToAllNodes', function(event, message){
scope.$broadcast(message.name, message.data);
});
Теперь в любом из дочерних узлов вам просто нужно $emit сообщение или перехватить его с помощью $on.
ЗАМЕЧАНИЕ: Обычно довольно легко установить связь в одном вложенном пути, не используя $emit, $broadcast или $on, что означает, что большинство сценариев использования касаются ситуаций, когда вы пытаетесь заставить узел 1 общаться с узлом 2 или наоборот.
Как узел 2 общается с узлом 1?
В файле two.js:
scope.$emit('pushChangesToAllNodes', sendNewChanges());
function sendNewChanges(){ // для какого-то события.
return { name: 'talkToOne', data: [1, 2, 3] };
}
В файле three.js - верхнем узле для всех дочерних узлов.
Мы уже рассмотрели этот пример, помните?
В файле one.js:
scope.$on('talkToOne', function(event, arrayOfNumbers){
arrayOfNumbers.forEach(function(number){
console.log(number);
});
});
Вам все еще нужно будет использовать $on для каждого конкретного значения, которое вы хотите перехватить, но теперь вы можете создать все, что хотите, в любом из узлов, не беспокоясь о том, как передать сообщение через пробел между родителями, поскольку мы перехватываем и передаем в эфир общее pushChangesToAllNodes.
Чтобы передать объект $scope
от одного контроллера к другому, я рассматриваю методы $rootScope.$broadcast
и $rootScope.$emit
, так как они наиболее часто используются.
Случай 1:
$rootScope.$broadcast
:
$rootScope.$broadcast('myEvent', $scope.data); // Здесь `myEvent` - это имя события
$rootScope.$on('myEvent', function(event, data) {
// обработчик события `myEvent`
});
Слушатели $rootScope
не уничтожаются автоматически. Их нужно уничтожить вручную с помощью $destroy
. Лучше использовать $scope.$on
, так как слушатели на уровне $scope
автоматически уничтожаются, т.е. как только $scope
разрушается.
$scope.$on('myEvent', function(event, data) {
// обработчик события
});
Или:
var customEventListener = $rootScope.$on('myEvent', function(event, data) {
// обработчик события
});
$scope.$on('$destroy', function() {
customEventListener();
});
Случай 2:
$rootScope.$emit
:
$rootScope.$emit('myEvent', $scope.data);
$rootScope.$on('myEvent', function(event, data) {
// `$scope.$on` не сработает
});
Основное отличие между $emit
и $broadcast
заключается в том, что событие $rootScope.$emit
должно обрабатываться с помощью $rootScope.$on
, поскольку вызываемое событие никогда не поднимается вверх по дереву областей видимости.
Также в этом случае необходимо уничтожить слушателя, как и в случае с $broadcast
.
Дополнение:
Я предпочитаю не использовать
$rootScope.$broadcast + $scope.$on
, а использовать$rootScope.$emit + $rootScope.$on
. Комбинация$rootScope.$broadcast + $scope.$on
может вызвать серьезные проблемы с производительностью, так как событие будет подниматься через все области видимости.
Дополнение 2:
Проблемы, поднимаемые в этом ответе, были решены в версии Angular.js 1.2.7.
$broadcast
теперь избегает всплытия через незарегистрированные области видимости и работает так же быстро, как и$emit
.
Для обмена событиями между контроллерами в одном приложении вам нужно использовать $rootScope
. Вам необходимо внедрить зависимость $rootScope
в ваши контроллеры. Вот рабочий пример:
app.controller('firstCtrl', function($scope, $rootScope) {
// В этом контроллере мы генерируем событие
$rootScope.$emit('someEvent', [1, 2, 3]);
});
app.controller('secondCtrl', function($scope, $rootScope) {
// В этом контроллере мы подписываемся на событие
$rootScope.$on('someEvent', function(event, data) {
console.log(data);
});
});
Обратите внимание, что события, связанные с объектом $scope
, будут работать только в родительском контроллере. Для взаимодействия между контроллерами используется либо $rootScope
, либо сервисы.
В чем разница между angular-route и angular-ui-router?
Как перенаправить на другую веб-страницу?
Как выполнить функцию JavaScript, имея её имя в виде строки
В чем разница между String.slice и String.substring?
Проверка соответствия строки регулярному выражению в JS