Як скасувати підписку на трансляцію події у angularJS. Як видалити функцію, зареєстровану через $ on


278

Я зареєстрував свого слухача на трансляції події $ за допомогою функції $ on

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

і я хочу скасувати реєстрацію цього слухача на основі певного ділового правила. Але моя проблема полягає в тому, що після її реєстрації я не в змозі скасувати її.

Чи є в AngularJS метод скасувати реєстрацію конкретного слухача? Метод на зразок $ для того, щоб скасувати цю подію, може бути $ off. Так що, виходячи з логіки бізнесу, я можу сказати

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

і цю функцію припиняють викликати, коли хтось транслює подію "onViewUpdated".

Дякую

EDIT : Я хочу скасувати реєстрацію слухача з іншої функції. Не функція, де я його реєструю.


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;
    };
},

Також дивіться документи .


Так. Після налагодження коду сортування я з'ясував, що є масив слухачів $$, який містить усі події та створив мою функцію $ off. Спасибі
Hitesh.Aneja

Який фактичний випадок використання, коли ви не можете використовувати передбачений кутовий спосіб зняття реєстрації? Чи скасування реєстрації в іншому обсязі не пов'язане з тією сферою, яку створив слухач?
Лівіу Т.

1
Так, я фактично видалив свою відповідь, бо не хочу плутати людей. Це правильний спосіб зробити це.
Бен Леш

3
@Liviu: Це стане головним болем із зростанням застосування. Це не тільки ця подія, є й багато інших подій, і не обов'язково, що я завжди знімаю реєстрацію в одній і тій же функції функції. Можуть бути випадки, коли я викликаю функцію, яка реєструє цього слухача, але скасовує реєстрацію слухача під час іншого виклику, навіть у тих випадках, я не отримаю посилання, якщо я не зберігаю їх поза моєю сферою дії. Тож для моєї нинішньої реалізації моя реалізація виглядає для мене життєздатним рішенням. Але, безумовно, хотілося б дізнатися причини, чому AngularJS зробив це таким чином.
Hitesh.Aneja

2
Я думаю, що Angular зробив це таким чином, оскільки багато часу вбудованих анонімних функцій використовуються як аргументи функції $ on. Для виклику $ range. $ Off (тип, функція) нам потрібно буде зберегти посилання на анонімну функцію. Це просто по-іншому подумати про те, як можна нормально додавати / видаляти слухачів подій такою мовою, як ActionScript або шаблон, що спостерігається на Java
dannrob

60

Переглядаючи більшість відповідей, вони здаються надмірно складними. Кутовий має вбудовані механізми для реєстрації.

Використовуйте функцію зняття реєстрації, яку повертає$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();
});

Настільки ж просто, що відповідей багато, але це більш стисло.
Девід Агілар

8
Технічно правильно, хоч і трохи оманливо, тому $scope.$onщо не потрібно реєструватись вручну $destroy. Кращим прикладом може бути використання $rootScope.$on.
hgoebl

2
найкраща відповідь, але хочете побачити більше пояснень, чому викликання цього слухача всередині $ знищує вбиває слухача.
Мохаммед Рафіг

1
@MohammadRafigh Заклик слухача до $ знищити саме те, де я вирішив його поставити. Якщо я правильно пам'ятаю, це був код, який я мав всередині директиви, і це мало сенс, що коли область директив знищується, слухачі повинні бути незареєстрованими.
long2know

@hgoebl Я не знаю, що ти маєш на увазі. Якщо у мене, наприклад, є директива, яка використовується в декількох місцях, і кожен реєструє слухача для події, як би мені допомогти $ rootScope. $? Здається, розпорядження директивою є найкращим місцем для розпорядження своїх слухачів.
long2know

26

Цей код працює для мене:

$rootScope.$$listeners.nameOfYourEvent=[];

1
Дивлячись на $ rootScope. Слухачі $$ - це також хороший спосіб спостерігати за життєвим циклом слухача та експериментувати з ним.
XML

Виглядає просто і чудово. Я думаю, що його щойно видалено посилання на функцію. чи не так?
Джей Шукла

26
Це рішення не рекомендується, оскільки член слухачів $$ вважається приватним. Фактично, будь-який член кутового об’єкта з префіксом '$$' є звичайним за умовою.
шовавник

5
Я б не рекомендував цей варіант, оскільки він видаляє всіх слухачів, а не лише того, кого потрібно видалити. Це може спричинити проблеми в майбутньому, якщо ви додасте іншого слухача до іншої частини сценарію.
Rainer Plumer

10

EDIT: Правильний спосіб зробити це у відповіді @ 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);

Дякую

EDIT: AngularJS спосіб зробити це у відповіді @ LiviuT! Але якщо ви хочете скасувати реєстрацію слухача в іншій області і в той же час хочете триматися осторонь від створення локальних змінних, щоб зберегти посилання на функцію дереєстрації. Це можливе рішення.


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. Мені цікаво про те, чи буде коли-небудь примірник належним чином знищений та зібраний сміття, враховуючи, що ви посилаєтесь на закриті змінні. Вам доведеться використати мільйон таких, щоб навіть це було проблемою пам'яті, але мені цікаво. Якщо хтось має якусь інформацію, будь ласка, поділіться.

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'); 
            });

ви можете викликати функцію відреєстрації (), щоб скасувати реєстрацію цього слухача


0

Один із способів - просто знищити слухача, коли ви закінчите з ним.

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.