Как отписаться от трансляции на angularJS. Как удалить функцию, зарегистрированную через $ on

278

Я зарегистрировал своего слушателя на событие трансляции $, используя функцию $ on

$scope.$on("onViewUpdated", this.callMe);

и я хочу отменить регистрацию этого слушателя на основе определенного бизнес-правила. Но моя проблема в том, что после регистрации я не могу отменить ее регистрацию.

Есть ли в AngularJS какой-либо метод для отмены регистрации конкретного слушателя? Такой метод, как $ для отмены регистрации этого события, может быть $ off. Так что на основе бизнес-логики я могу сказать

 $scope.$off("onViewUpdated", this.callMe);

и эта функция перестает вызываться, когда кто-то передает событие «onViewUpdated».

Спасибо

РЕДАКТИРОВАТЬ : Я хочу отменить регистрацию слушателя от другой функции. Не та функция, где я это регистрирую.

Hitesh.Aneja
источник
2
Для всех, кто интересуется, возвращенная функция задокументирована здесь
Fagner Brack

Ответы:

477

Вам необходимо сохранить возвращенную функцию и вызвать ее, чтобы отписаться от события.

var deregisterListener = $scope.$on("onViewUpdated", callMe);
deregisterListener (); // this will deregister that listener

Это найдено в исходном коде :) по крайней мере в 1.0.4. Я просто выложу полный код, так как он короткий

/**
  * @param {string} name Event name to listen on.
  * @param {function(event)} listener Function to call when the event is emitted.
  * @returns {function()} Returns a deregistration function for this listener.
  */
$on: function(name, listener) {
    var namedListeners = this.$$listeners[name];
    if (!namedListeners) {
      this.$$listeners[name] = namedListeners = [];
    }
    namedListeners.push(listener);

    return function() {
      namedListeners[indexOf(namedListeners, listener)] = null;
    };
},

Также смотрите документы .

Ливиу Т.
источник
Да. После отладки кода sorce я обнаружил, что существует массив $$ listeners, который содержит все события, и создал мою функцию $ off. Спасибо
Hitesh.Aneja
В каком конкретном случае вы не можете использовать предоставленный угловой способ отмены регистрации? Разъединение, выполненное в другой области, не связано с областью, которая создала слушателя?
Ливиу Т.
1
Да, я фактически удалил свой ответ, потому что я не хочу вводить людей в заблуждение. Это правильный способ сделать это.
Бен Леш
3
@Liviu: Это станет головной болью с ростом приложения. Не только это событие, но и множество других событий, и не обязательно, что я всегда отменяю регистрацию в одной и той же функции видимости. Могут быть случаи, когда я вызываю функцию, которая регистрирует этого слушателя, но отменяет регистрацию слушателя на другом вызове, даже в тех случаях я не получу ссылку, если не сохраню их вне моей области. Так что для моей текущей реализации моя реализация выглядит жизнеспособным решением для меня. Но определенно хотел бы знать причины, по которым AngularJS сделал это таким образом.
Hitesh.Aneja
2
Я думаю, что Angular сделал это таким образом, потому что многие встроенные анонимные функции времени используются в качестве аргументов функции $ on. Чтобы вызвать $ scope. $ Off (тип, функция), нам нужно сохранить ссылку на анонимную функцию. Это просто другой взгляд на то, как обычно можно добавлять / удалять прослушиватели событий на языке, таком как ActionScript или шаблон Observable в Java
dannrob
60

Глядя на большинство ответов, они кажутся слишком сложными. Angular имеет встроенные механизмы для отмены регистрации.

Используйте функцию отмены регистрации, возвращенную$on :

// Register and get a handle to the listener
var listener = $scope.$on('someMessage', function () {
    $log.log("Message received");
});

// Unregister
$scope.$on('$destroy', function () {
    $log.log("Unregistering listener");
    listener();
});
long2know
источник
Так же просто, как здесь, есть много ответов, но это более кратко.
Давид Агилар,
8
Технически правильно, хотя и немного вводит в заблуждение, потому $scope.$onчто не нужно регистрироваться вручную $destroy. Лучшим примером будет использование $rootScope.$on.
hgoebl
2
лучший ответ, но хочу увидеть больше объяснений о том, почему вызов этого слушателя внутри $ destroy убивает слушателя.
Мохаммед Рафиг
1
@MohammadRafigh Вызов слушателя внутри $ destroy - это именно то, где я решил его разместить. Если я правильно помню, это был код, который я имел внутри директивы, и имел смысл, что когда область действия директив была уничтожена, слушатели должны быть незарегистрированными.
long2know
@hgoebl Я не знаю, что ты имеешь в виду. Если у меня есть, например, директива, которая используется в нескольких местах, и каждая из них регистрирует слушателя события, как бы мне помогло использование $ rootScope. $ On? Распределение объема директивы, кажется, лучшее место, чтобы избавиться от ее слушателей.
long2know
26

Этот код работает для меня:

$rootScope.$$listeners.nameOfYourEvent=[];
Рустем Мусабеков
источник
1
Глядя на $ rootScope. $$ listeners также является хорошим способом наблюдать жизненный цикл слушателя и экспериментировать с ним.
XML
Выглядит просто и замечательно. Я думаю, что его только что удалили ссылку на функцию. не так ли?
Джей Шукла
26
Это решение не рекомендуется, поскольку слушатель $$ считается личным. Фактически, любой член углового объекта с префиксом $$ является частным по соглашению.
Шовавник
5
Я бы не рекомендовал эту опцию, потому что она удаляет всех слушателей, а не только того, кого нужно удалить. Это может вызвать проблемы в будущем, когда вы добавите другого слушателя в другую часть скрипта.
Райнер Плумер
10

РЕДАКТИРОВАТЬ: правильный способ сделать это в ответе @ LiviuT!

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

//A little hack to add an $off() method to $scopes.
(function () {
  var injector = angular.injector(['ng']),
      rootScope = injector.get('$rootScope');
      rootScope.constructor.prototype.$off = function(eventName, fn) {
        if(this.$$listeners) {
          var eventArr = this.$$listeners[eventName];
          if(eventArr) {
            for(var i = 0; i < eventArr.length; i++) {
              if(eventArr[i] === fn) {
                eventArr.splice(i, 1);
              }
            }
          }
        }
      }
}());

И вот как это будет работать:

  function myEvent() {
    alert('test');
  }
  $scope.$on('test', myEvent);
  $scope.$broadcast('test');
  $scope.$off('test', myEvent);
  $scope.$broadcast('test');

И вот плункер этого в действии

Бен Леш
источник
Работал как шарм! но я немного отредактировал его и положил в секцию
.run
Люблю это решение. Делает для намного более чистого решения - так намного легче читать. +1
Рик
7

После отладки кода я создал свою собственную функцию, аналогичную ответу "blesh". Так вот что я сделал

MyModule = angular.module('FIT', [])
.run(function ($rootScope) {
        // Custom $off function to un-register the listener.
        $rootScope.$off = function (name, listener) {
            var namedListeners = this.$$listeners[name];
            if (namedListeners) {
                // Loop through the array of named listeners and remove them from the array.
                for (var i = 0; i < namedListeners.length; i++) {
                    if (namedListeners[i] === listener) {
                        return namedListeners.splice(i, 1);
                    }
                }
            }
        }
});

поэтому, добавив мою функцию к $ rootcope, теперь она доступна для всех моих контроллеров.

и в моем коде я делаю

$scope.$off("onViewUpdated", callMe);

Спасибо

РЕДАКТИРОВАТЬ: AngularJS способ сделать это в ответе @ LiviuT! Но если вы хотите отменить регистрацию слушателя в другой области и в то же время хотите избежать создания локальных переменных, чтобы сохранить ссылки на функцию отмены регистрации. Это возможное решение.

Hitesh.Aneja
источник
1
Я на самом деле удаляю свой ответ, потому что ответ @ LiviuT на 100% правильный.
Бен Леш
Ответ @blesh LiviuT является правильным и, в действительности, англоязычным подходом к отмене регистрации, но он плохо подходит для сценариев, в которых вы должны отменить регистрацию слушателя в другой области. Так что это простая альтернатива.
Hitesh.Aneja
1
Он обеспечивает то же подключение, что и любое другое решение. Вы бы просто поместили переменную, содержащую функцию уничтожения, во внешнее замыкание или даже в глобальную коллекцию ... или где угодно.
Бен Леш,
Я не хочу продолжать создавать глобальные переменные для хранения ссылок на функции отмены регистрации, а также не вижу проблем с использованием моей собственной функции $ off.
Hitesh.Aneja
1

Ответ @ LiviuT потрясающий, но, похоже, многие люди задаются вопросом, как повторно получить доступ к функции разрыва обработчика из другой области действия или функции $, если вы хотите уничтожить ее из места, отличного от того, где оно было создано. Ответ @ Рустем Мусабеков работает просто замечательно, но не очень идиоматично. (И полагается на то, что должно быть частной реализацией, которая может измениться в любое время.) И с этого момента, это становится все более сложным ...

Я думаю, что простой ответ здесь - просто передать ссылку на функцию разрыва ( offCallMeFnв его примере) в самом обработчике, а затем вызвать ее на основании некоторого условия; возможно, аргумент, который вы включаете в событие, которое вы $ широковещательно или $ излучаете. Таким образом, обработчики могут сносить себя, когда вы хотите, где угодно, неся семена своего собственного уничтожения. Вот так:

// Creation of our handler:
var tearDownFunc = $rootScope.$on('demo-event', function(event, booleanParam) {
    var selfDestruct = tearDownFunc;
    if (booleanParam === false) {
        console.log('This is the routine handler here. I can do your normal handling-type stuff.')
    }
    if (booleanParam === true) {
        console.log("5... 4... 3... 2... 1...")
        selfDestruct();
    }
});

// These two functions are purely for demonstration
window.trigger = function(booleanArg) {
    $scope.$emit('demo-event', booleanArg);
}
window.check = function() {
    // shows us where Angular is stashing our handlers, while they exist
    console.log($rootScope.$$listeners['demo-event'])
};

// Interactive Demo:

>> trigger(false);
// "This is the routine handler here. I can do your normal handling-type stuff."

>> check();
// [function] (So, there's a handler registered at this point.)  

>> trigger(true);
// "5... 4... 3... 2... 1..."

>> check();
// [null] (No more handler.)

>> trigger(false);
// undefined (He's dead, Jim.)

Две мысли:

  1. Это отличная формула для однократного запуска обработчика. Просто бросьте условия и бегите, selfDestructкак только он завершит свою миссию самоубийства.
  2. Интересно, будет ли когда-либо исходная область действия должным образом уничтожена и собрана мусором, учитывая, что вы несете ссылки на закрытые переменные. Вы должны использовать миллион из них, чтобы даже иметь проблемы с памятью, но мне любопытно. Если у кого-то есть понимание, пожалуйста, поделитесь.
XML
источник
1

Зарегистрируйте ловушку, чтобы отписаться от ваших слушателей, когда компонент удален:

$scope.$on('$destroy', function () {
   delete $rootScope.$$listeners["youreventname"];
});  
Дима
источник
Хотя это не общепринятый способ сделать это, иногда это необходимое решение.
Тони Брасунас
1

В случае, если вам нужно включить и выключить слушатель несколько раз, вы можете создать функцию с booleanпараметром

function switchListen(_switch) {
    if (_switch) {
      $scope.$on("onViewUpdated", this.callMe);
    } else {
      $rootScope.$$listeners.onViewUpdated = [];
    }
}
Богатый
источник
0

«$ on» само по себе возвращает функцию для отмены регистрации

 var unregister=  $rootScope.$on('$stateChangeStart',
            function(event, toState, toParams, fromState, fromParams, options) { 
                alert('state changing'); 
            });

Вы можете вызвать функцию unregister (), чтобы отменить регистрацию слушателя.

Раджив
источник
0

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

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
user1502826
источник