9

Работа с $scope.$emit и $scope.$on в AngularJS

6

Как я могу передать свой объект $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 ответ(ов)

15

Первым делом стоит отметить, что связь между родительским и дочерним скоупами имеет значение. У вас есть две возможности для эмита события:

  • $broadcast — отправляет событие вниз ко всем дочерним скоупам.
  • $emit — отправляет событие вверх по иерархии скоупов.

Не зная специфики отношений между вашими контроллерами (скоупами), можно выделить несколько вариантов:

  1. Если скоуп 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); });
    }
    
  2. В случае, если между вашими скоупами нет родительской и дочерней связи, вы можете внедрить $rootScope в контроллер и отправлять событие всем дочерним скоупам (включая и secondCtrl):

    function firstCtrl($rootScope)
    {
        $rootScope.$broadcast('someEvent', [1, 2, 3]);
    }
    
  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]);
    }
    

Таким образом, выберите подходящий для вашей ситуации способ передачи события.

1

Я бы также предложил четвертый вариант, который является более подходящей альтернативой предложенным вариантам от @zbynour.

Используйте $rootScope.$emit, а не $rootScope.$broadcast, независимо от отношения между передающим и принимающим контроллерами. Таким образом, событие останется в наборе $rootScope.$$listeners, тогда как при использовании $rootScope.$broadcast событие будет распространяться на все дочерние области видимости, и большинство из них, скорее всего, не будут слушателями этого события. Конечно, в принимающем контроллере вам просто нужно использовать $rootScope.$on.

Важно помнить о том, чтобы уничтожить слушатели корневого Scope контроллера:

var unbindEventHandler = $rootScope.$on('myEvent', myHandler);
$scope.$on('$destroy', function () {
  unbindEventHandler();
});

Обратите внимание, что это поможет избежать утечек памяти, когда контроллер больше не используется.

1

Вы можете передавать любой объект в иерархии вашего приложения, включая $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.

0

Чтобы передать объект $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.

0

Для обмена событиями между контроллерами в одном приложении вам нужно использовать $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, либо сервисы.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь