So melden Sie ein Broadcast-Ereignis in angleJS ab. So entfernen Sie die über $ on registrierte Funktion

278

Ich habe meinen Listener mit der Funktion $ on für ein $ Broadcast-Ereignis registriert

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

und ich möchte diesen Listener basierend auf einer bestimmten Geschäftsregel abmelden. Mein Problem ist jedoch, dass ich die Registrierung nach der Registrierung nicht mehr aufheben kann.

Gibt es in AngularJS eine Methode, um die Registrierung eines bestimmten Listeners aufzuheben? Eine Methode wie $, mit der dieses Ereignis abgemeldet wird, ist möglicherweise $ off. Das kann ich also basierend auf der Geschäftslogik sagen

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

und diese Funktion wird nicht mehr aufgerufen, wenn jemand das Ereignis "onViewUpdated" sendet.

Vielen Dank

EDIT : Ich möchte den Listener von einer anderen Funktion abmelden. Nicht die Funktion, bei der ich sie registriere.

Hitesh.Aneja
quelle
2
Für alle , fragen, ist die zurückgegebene Funktion dokumentiert hier
Fagner Brack

Antworten:

477

Sie müssen die zurückgegebene Funktion speichern und aufrufen, um sich vom Ereignis abzumelden.

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

Dies ist im Quellcode zu finden :) zumindest in 1.0.4. Ich werde nur den vollständigen Code posten, da er kurz ist

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

Siehe auch die Dokumente .

Liviu T.
quelle
Ja. Nach dem Debuggen des Sorce-Codes fand ich heraus, dass es ein $$ Listener-Array gibt, das alle Ereignisse enthält, und erstellte meine $ off-Funktion. Danke
Hitesh.Aneja
Was ist der tatsächliche Anwendungsfall, bei dem Sie die bereitgestellte eckige Art der Abmeldung nicht verwenden können? Wird die Abmeldung in einem anderen Bereich vorgenommen, der nicht mit dem Bereich verknüpft ist, der den Listener erstellt hat?
Liviu T.
1
Ja, ich habe meine Antwort tatsächlich gelöscht, weil ich die Leute nicht verwirren möchte. Dies ist der richtige Weg, dies zu tun.
Ben Lesh
3
@Liviu: Das wird mit zunehmender Anwendung zu Kopfschmerzen. Es ist nicht nur dieses Ereignis, es gibt auch viele andere Ereignisse und nicht unbedingt, dass ich mich immer in derselben Bereichsfunktion abmelde. Es kann Fälle geben, in denen ich eine Funktion aufrufe, die diesen Listener registriert, aber den Listener bei einem anderen Aufruf abmeldet. Selbst in diesen Fällen erhalte ich die Referenz nur, wenn ich sie außerhalb meines Bereichs speichere. Für meine aktuelle Implementierung scheint meine Implementierung für mich eine tragfähige Lösung zu sein. Aber ich würde auf jeden Fall gerne wissen, warum AngularJS das so gemacht hat.
Hitesh.Aneja
2
Ich denke, dass Angular es so gemacht hat, weil häufig anonyme Inline-Funktionen als Argumente für die $ on-Funktion verwendet werden. Um $ scope. $ Off (Typ, Funktion) aufzurufen, müssten wir einen Verweis auf die anonyme Funktion behalten. Es geht nur darum, anders zu denken, als man normalerweise Ereignis-Listener in einer Sprache wie ActionScript oder dem Observable-Muster in Java hinzufügt / entfernt
dannrob
60

Bei den meisten Antworten scheinen sie zu kompliziert zu sein. Angular hat Mechanismen zum Aufheben der Registrierung eingebaut.

Verwenden Sie die Abmeldungsfunktion, die zurückgegeben wird von$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
quelle
So einfach wie diese, es gibt viele Antworten, aber dies ist prägnanter.
David Aguilar
8
Technisch korrekt, wenn auch etwas irreführend, da $scope.$ondie Registrierung nicht manuell aufgehoben werden muss $destroy. Ein besseres Beispiel wäre die Verwendung von a $rootScope.$on.
Hgoebl
2
Beste Antwort, aber ich möchte mehr Erklärungen darüber sehen, warum das Aufrufen dieses Listeners in $ destroy den Listener tötet.
Mohammad Rafigh
1
@MohammadRafigh Wenn ich den Listener innerhalb von $ destroy anrufe, habe ich mich genau dafür entschieden. Wenn ich mich richtig erinnere, war dies Code, den ich in einer Direktive hatte, und es machte Sinn, dass Listener nicht registriert werden sollten, wenn der Geltungsbereich der Direktiven zerstört wurde.
Long2know
@hgoebl Ich weiß nicht was du meinst. Wenn ich zum Beispiel eine Direktive habe, die an mehreren Stellen verwendet wird und jeder einen Listener für ein Ereignis registriert, wie würde mir die Verwendung von $ rootScope. $ On helfen? Die Geltungsbereichsbeseitigung der Richtlinie scheint der beste Ort zu sein, um ihre Zuhörer zu entsorgen.
Long2know
26

Dieser Code funktioniert für mich:

$rootScope.$$listeners.nameOfYourEvent=[];
Rustem Mussabekov
quelle
1
Ein Blick auf $ rootScope. $$ Listener ist auch eine gute Möglichkeit, den Lebenszyklus des Listeners zu beobachten und damit zu experimentieren.
XML
Sieht einfach und gut aus. Ich denke, es ist gerade entfernte Referenz der Funktion. ist es nicht?
Jay Shukla
26
Diese Lösung wird nicht empfohlen, da das $$ Listener-Mitglied als privat betrachtet wird. Tatsächlich ist jedes Mitglied eines eckigen Objekts mit dem Präfix '$$' gemäß Konvention privat.
Shovavnik
5
Ich würde diese Option nicht empfehlen, da sie alle Listener entfernt, nicht nur die, die Sie entfernen müssen. Dies kann in Zukunft zu Problemen führen, wenn Sie einen anderen Listener in einem anderen Teil des Skripts hinzufügen.
Rainer Plumer
10

EDIT: Der richtige Weg, dies zu tun, ist in der Antwort von @ LiviuT!

Sie können den Bereich von Angular jederzeit erweitern, um solche Listener wie folgt zu entfernen:

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

Und so würde es funktionieren:

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

Und hier ist ein Plunker davon in Aktion

Ben Lesh
quelle
Lief wie am Schnürchen! aber ich habe es ein wenig bearbeitet und es in die .run-Sektion gestellt
aimiliano
Ich liebe diese Lösung. Ermöglicht eine viel sauberere Lösung - viel einfacher zu lesen. +1
Rick
7

Nach dem Debuggen des Codes habe ich meine eigene Funktion erstellt, genau wie die Antwort von "blesh". Das habe ich also getan

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

Wenn Sie meine Funktion jetzt an $ Rootscope anhängen, steht sie allen meinen Controllern zur Verfügung.

und in meinem Code mache ich

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

Vielen Dank

EDIT: Der AngularJS-Weg, dies zu tun, ist in der Antwort von @ LiviuT! Wenn Sie jedoch den Listener in einem anderen Bereich abmelden möchten und gleichzeitig keine lokalen Variablen erstellen möchten, um Verweise auf die Abmeldefunktion beizubehalten. Dies ist eine mögliche Lösung.

Hitesh.Aneja
quelle
1
Ich lösche meine Antwort tatsächlich, weil die Antwort von @ LiviuT zu 100% korrekt ist.
Ben Lesh
Die Antwort von @blesh LiviuT ist korrekt und tatsächlich ein angualarer Ansatz zur Abmeldung, aber nicht gut für die Szenarien geeignet, in denen Sie den Listener in einem anderen Bereich abmelden müssen. Das ist also eine einfache Alternative.
Hitesh.Aneja
1
Es bietet den gleichen Anschluss wie jede andere Lösung. Sie haben die Variable, die die Zerstörungsfunktion enthält, einfach in einen Außenverschluss oder sogar in eine globale Sammlung eingefügt ... oder an einen beliebigen Ort.
Ben Lesh
Ich möchte keine globalen Variablen erstellen, um Verweise auf die Abmeldefunktionen beizubehalten, und ich sehe auch keine Probleme bei der Verwendung meiner eigenen $ off-Funktion.
Hitesh.Aneja
1

Die Antwort von @ LiviuT ist fantastisch, aber es scheint, als würden sich viele Leute fragen, wie sie von einem anderen $ -Bereich oder einer anderen Funktion aus wieder auf die Abreißfunktion des Handlers zugreifen können, wenn Sie sie von einem anderen Ort als dem, an dem sie erstellt wurde, zerstören möchten. @ Рустем Мусабековs Antwort funktioniert einfach gut, ist aber nicht sehr idiomatisch. (Und hängt davon ab, was ein privates Implementierungsdetail sein soll, das sich jederzeit ändern kann.) Und von da an wird es nur noch komplizierter ...

Ich denke, die einfache Antwort hier besteht darin, einfach einen Verweis auf die Abreißfunktion ( offCallMeFnin seinem Beispiel) im Handler selbst zu führen und sie dann basierend auf einer bestimmten Bedingung aufzurufen. Vielleicht ein Argument, das Sie in das Ereignis aufnehmen, das Sie senden oder ausstrahlen. Handler können sich so abreißen, wann immer Sie wollen, wo immer Sie wollen, und die Samen ihrer eigenen Zerstörung mit sich herumtragen. Wie so:

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

Zwei Gedanken:

  1. Dies ist eine großartige Formel für einen einmaligen Handler. Lassen Sie einfach die Bedingungen fallen und rennen Sie selfDestruct, sobald die Selbstmordmission abgeschlossen ist.
  2. Ich frage mich, ob der ursprüngliche Bereich jemals ordnungsgemäß zerstört und Müll gesammelt wird, da Sie Verweise auf geschlossene Variablen enthalten. Sie müssten eine Million davon verwenden, um überhaupt ein Speicherproblem zu haben, aber ich bin neugierig. Wenn jemand einen Einblick hat, teilen Sie ihn bitte mit.
XML
quelle
1

Registrieren Sie einen Hook, um Ihre Listener abzumelden, wenn die Komponente entfernt wird:

$scope.$on('$destroy', function () {
   delete $rootScope.$$listeners["youreventname"];
});  
Dima
quelle
Dies ist zwar nicht die allgemein akzeptierte Methode, aber manchmal ist dies die notwendige Lösung.
Tony Brasunas
1

Falls Sie den Listener mehrmals ein- und ausschalten müssen, können Sie eine Funktion mit booleanParameter erstellen

function switchListen(_switch) {
    if (_switch) {
      $scope.$on("onViewUpdated", this.callMe);
    } else {
      $rootScope.$$listeners.onViewUpdated = [];
    }
}
Reich
quelle
0

'$ on' selbst gibt die Funktion zum Aufheben der Registrierung zurück

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

Sie können die Funktion unregister () aufrufen, um die Registrierung dieses Listeners aufzuheben

Rajiv
quelle
0

Eine Möglichkeit besteht darin, den Hörer einfach zu zerstören, sobald Sie damit fertig sind.

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
user1502826
quelle