Jak zrezygnować z transmisji wydarzenia w angularJS. Jak usunąć funkcję zarejestrowaną przez $ on

278

Zarejestrowałem mojego słuchacza na zdarzenie emisji $ za pomocą funkcji $ on

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

i chcę wyrejestrować tego odbiornika na podstawie określonej reguły biznesowej. Ale moim problemem jest to, że po zarejestrowaniu nie mogę go wyrejestrować.

Czy w AngularJS jest jakaś metoda wyrejestrowania konkretnego odbiornika? Metodą taką jak $ przy tym wyrejestrowaniu tego zdarzenia może być $ off. Opierając się na logice biznesowej, mogę powiedzieć

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

i ta funkcja przestaje być wywoływana, gdy ktoś transmituje zdarzenie „onViewUpdated”.

Dzięki

EDYCJA : Chcę wyrejestrować słuchacza z innej funkcji. Nie jest to funkcja, w której ją rejestruję.

Hitesh.Aneja
źródło
2
Na każdy zastanawiając się, funkcja zwrócony jest udokumentowane tutaj
Fagner Brack

Odpowiedzi:

477

Musisz zapisać zwróconą funkcję i wywołać ją, aby anulować subskrypcję zdarzenia.

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

Można to znaleźć w kodzie źródłowym :) przynajmniej w wersji 1.0.4. Po prostu opublikuję pełny kod, ponieważ jest krótki

/**
  * @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;
    };
},

Zobacz także dokumenty .

Liviu T.
źródło
Tak. Po debugowaniu kodu magii odkryłem, że istnieje tablica nasłuchiwania $$, która ma wszystkie zdarzenia i utworzyła moją funkcję $ off. Dzięki
Hitesh.Aneja
Jaki jest rzeczywisty przypadek użycia, w którym nie można użyć podanego kątowego sposobu wyrejestrowania? Czy wyrejestrowanie odbywa się w innym zakresie, który nie jest powiązany z zakresem, który utworzył nasłuchiwanie?
Liviu T.,
1
Tak, właściwie usunąłem swoją odpowiedź, ponieważ nie chcę mylić ludzi. To jest właściwy sposób, aby to zrobić.
Ben Lesh
3
@Liviu: To będzie ból głowy z rosnącą aplikacją. To nie tylko to wydarzenie, ale także wiele innych wydarzeń i niekoniecznie to, że zawsze wyrejestrowuję się w tej samej funkcji. Mogą wystąpić przypadki, gdy wywołuję funkcję, która rejestruje tego odbiornika, ale wyrejestrowuje go przy innym wywołaniu, nawet w tych przypadkach nie otrzymam referencji, chyba że przechowuję je poza moim zakresem. Więc dla mojej obecnej implementacji moja implementacja wygląda dla mnie realnym rozwiązaniem. Ale zdecydowanie chciałbym poznać powody, dla których AngularJS zrobił to w ten sposób.
Hitesh.Aneja
2
Myślę, że Angular zrobił to w ten sposób, ponieważ wiele wbudowanych funkcji anonimowych jest używanych jako argumenty funkcji $ on. Aby wywołać $ scope. $ Off (typ, funkcja), musielibyśmy zachować odwołanie do funkcji anonimowej. To po prostu inny sposób myślenia niż zwykłe dodawanie / usuwanie detektorów zdarzeń w języku takim jak ActionScript lub wzorzec obserwowalny w Javie
dannrob
60

Patrząc na większość odpowiedzi, wydają się one zbyt skomplikowane. Angular ma wbudowane mechanizmy do wyrejestrowania.

Użyj funkcji wyrejestrowania zwróconej przez$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
źródło
Tak proste jak te, istnieje wiele odpowiedzi, ale jest to bardziej zwięzłe.
David Aguilar
8
Technicznie poprawne, choć nieco mylące, ponieważ $scope.$onnie trzeba ręcznie rejestrować $destroy. Lepszym przykładem byłoby użycie $rootScope.$on.
hgoebl
2
najlepsza odpowiedź, ale chcesz dowiedzieć się więcej o tym, dlaczego wywołanie tego słuchacza wewnątrz $ destroy zabija go.
Mohammad Rafigh
1
@MohammadRafigh Dzwonienie do słuchacza wewnątrz $ destroy to miejsce, w którym postanowiłem go umieścić. Jeśli dobrze pamiętam, był to kod, który miałem w dyrektywie, i miało sens, że kiedy zakres dyrektyw zostanie zniszczony, słuchacze powinni zostać wyrejestrowani.
long2know
@hgoebl Nie wiem o co ci chodzi. Jeśli mam na przykład dyrektywę, która jest używana w wielu miejscach, a każda rejestruje detektor dla zdarzenia, to w jaki sposób można użyć $ rootScope. $ On help? Wydobycie zakresu dyrektywy wydaje się najlepszym miejscem do pozbycia się słuchaczy.
long2know
26

Ten kod działa dla mnie:

$rootScope.$$listeners.nameOfYourEvent=[];
Rustem Mussabekov
źródło
1
Patrząc na $ rootScope. $$ nasłuchiwacze to także dobry sposób na obserwowanie cyklu życia słuchacza i eksperymentowanie z nim.
XML
Wygląda prosto i świetnie. Myślę, że właśnie usunięto odwołanie do funkcji. prawda?
Jay Shukla,
26
To rozwiązanie nie jest zalecane, ponieważ członek nasłuchujący $$ jest uważany za prywatny. W rzeczywistości każdy element obiektu kątowego z przedrostkiem „$$” jest umownie prywatny.
shovavnik
5
Nie poleciłbym tej opcji, ponieważ usuwa ona wszystkie nasłuchiwania, nie tylko te, które trzeba usunąć. Może to powodować problemy w przyszłości, gdy dodasz innego detektora w innej części skryptu.
Rainer Plumer,
10

EDYCJA: Prawidłowym sposobem na to jest odpowiedź @ LiviuT!

Zawsze możesz rozszerzyć zakres Angulara, aby umożliwić Ci usunięcie takich detektorów:

//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);
              }
            }
          }
        }
      }
}());

A oto jak to by działało:

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

A oto kawałek tego w akcji

Ben Lesh
źródło
Działa jak urok! ale trochę go zredagowałem i umieściłem w sekcji
.run
Uwielbiam to rozwiązanie. Sprawia, że ​​rozwiązanie jest znacznie czystsze - o wiele łatwiejsze do odczytania. +1
Rick
7

Po debugowaniu kodu stworzyłem własną funkcję, podobnie jak odpowiedź „blesh”. Tak właśnie zrobiłem

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

więc dołączając moją funkcję do $ rootscope, jest ona teraz dostępna dla wszystkich moich kontrolerów.

i robię w moim kodzie

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

Dzięki

EDYCJA: Sposób AngularJS na to jest w odpowiedzi @ LiviuT! Ale jeśli chcesz wyrejestrować nasłuchiwanie w innym zakresie i jednocześnie nie chcesz tworzyć lokalnych zmiennych, aby zachować odwołania do funkcji wyrejestrowania. To możliwe rozwiązanie.

Hitesh.Aneja
źródło
1
Właściwie usuwam swoją odpowiedź, ponieważ odpowiedź @ LiviuT jest w 100% poprawna.
Ben Lesh
@ rozwiązywanie problemów Odpowiedź LiviuT jest poprawna i właściwie oparta na angualarach podejście do wyrejestrowania, ale nie łączy się dobrze w scenariuszach, w których trzeba wyrejestrować słuchacza w innym zakresie. Jest to więc łatwa alternatywa.
Hitesh.Aneja
1
Zapewnia taki sam sposób podłączenia, jak każde inne rozwiązanie. Po prostu umieściłeś zmienną zawierającą funkcję niszczenia w zewnętrznym zamknięciu, a nawet w globalnej kolekcji ... lub gdziekolwiek chcesz.
Ben Lesh
Nie chcę nadal tworzyć zmiennych globalnych, aby zachować odwołania do funkcji wyrejestrowania, a także nie widzę problemów z używaniem własnej funkcji $ off.
Hitesh.Aneja
1

Odpowiedź @ LiviuT jest niesamowita, ale wydaje się, że wielu ludzi zastanawia się, jak ponownie uzyskać dostęp do funkcji usuwania funkcji obsługi z innego zakresu lub funkcji $, jeśli chcesz ją zniszczyć z miejsca innego niż to, w którym została utworzona. @ Рустем Мусабеков odpowiedź działa po prostu świetnie, ale nie jest bardzo idiomatyczna. (I opiera się na czymś, co powinno być prywatnym szczegółem implementacji, który może się zmienić w dowolnym momencie.) Od tego momentu staje się to bardziej skomplikowane ...

Myślę, że najłatwiejszą odpowiedzią jest po prostu przeniesienie odwołania do funkcji usuwania ( offCallMeFnw jego przykładzie) w samym module obsługi, a następnie wywołanie jej na podstawie pewnych warunków; być może argument, który dołączasz do wydarzenia, które emitujesz lub emitujesz. W ten sposób handlowcy mogą się zburzyć, kiedy tylko chcesz, gdziekolwiek chcesz, niosąc nasiona własnego zniszczenia. Tak jak:

// 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.)

Dwie myśli:

  1. To świetna formuła dla programu uruchamiającego po uruchomieniu. Porzuć warunkowe i biegnij, selfDestructjak tylko zakończy się samobójcza misja.
  2. Zastanawiam się, czy źródłowy zakres kiedykolwiek zostanie odpowiednio zniszczony i wyrzucony w pamięci, biorąc pod uwagę, że masz odniesienia do zmiennych zamkniętych. Trzeba by użyć miliona z nich, aby nawet mieć problem z pamięcią, ale jestem ciekawy. Jeśli ktoś ma jakikolwiek wgląd, prosimy o udostępnienie.
XML
źródło
1

Zarejestruj hak, aby wypisać się ze słuchaczy, gdy składnik zostanie usunięty:

$scope.$on('$destroy', function () {
   delete $rootScope.$$listeners["youreventname"];
});  
Dima
źródło
Chociaż nie jest to ogólnie akceptowany sposób, bywa, jest to konieczne rozwiązanie.
Tony Brasunas,
1

W przypadku konieczności wielokrotnego włączania i wyłączania nasłuchiwania można utworzyć funkcję z booleanparametrem

function switchListen(_switch) {
    if (_switch) {
      $scope.$on("onViewUpdated", this.callMe);
    } else {
      $rootScope.$$listeners.onViewUpdated = [];
    }
}
Bogaty
źródło
0

Sam $ $ zwraca funkcję do wyrejestrowania

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

możesz wywołać funkcję unregister (), aby wyrejestrować tego odbiornika

Rajiv
źródło
0

Jednym ze sposobów jest po prostu zniszczenie słuchacza, gdy skończysz.

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
użytkownik1502826
źródło