Come annullare l'iscrizione a un evento di trasmissione in angularJS. Come rimuovere la funzione registrata tramite $ on


278

Ho registrato il mio ascoltatore a un evento $ broadcast utilizzando la funzione $ on

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

e desidero annullare la registrazione di questo listener in base a una particolare regola aziendale. Ma il mio problema è che una volta registrato non sono in grado di cancellarlo.

Esiste un metodo in AngularJS per annullare la registrazione di un particolare ascoltatore? Un metodo come $ on che annulla la registrazione di questo evento potrebbe essere $ off. Quindi, in base alla logica aziendale, posso dire

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

e questa funzione smette di essere chiamata quando qualcuno trasmette l'evento "onViewUpdated".

Grazie

EDIT : voglio annullare la registrazione dell'ascoltatore da un'altra funzione. Non la funzione in cui lo registro.


2
Per chiunque si chieda, la funzione restituita è documentata qui
Fagner Brack

Risposte:


481

È necessario memorizzare la funzione restituita e chiamarla per annullare l'iscrizione all'evento.

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

Questo si trova nel codice sorgente :) almeno nella 1.0.4. Pubblicherò semplicemente il codice completo poiché è breve

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

Inoltre, vedere la documentazione .


Sì. Dopo aver eseguito il debug del codice sorce, ho scoperto che esiste un array $$ listener che ha tutti gli eventi e ha creato la mia funzione $ off. Grazie
Hitesh.Aneja

Qual è il caso d'uso effettivo in cui non è possibile utilizzare il modo angolare fornito per annullare la registrazione? L'annullamento della registrazione viene eseguito in un altro ambito non collegato all'ambito che ha creato il listener?
Liviu T.

1
Sì, in realtà ho cancellato la mia risposta perché non voglio confondere le persone. Questo è il modo corretto per farlo.
Ben Lesh

3
@ Liviu: diventerà un mal di testa con la crescente applicazione. Non è solo questo evento, ci sono anche molti altri eventi e non è detto che mi annulli sempre la registrazione nella stessa funzione di ambito. Potrebbero verificarsi casi in cui chiamo una funzione che sta registrando questo listener ma annullando la registrazione dell'ascoltatore su un'altra chiamata, anche in quei casi non otterrò il riferimento a meno che non li memorizzi al di fuori del mio ambito. Quindi per la mia attuale implementazione la mia implementazione mi sembra una soluzione praticabile. Ma sicuramente vorrei conoscere i motivi per cui AngularJS lo ha fatto in questo modo.
Hitesh.Aneja

2
Penso che Angular abbia fatto in questo modo perché la maggior parte delle volte le funzioni anonime inline vengono utilizzate come argomenti della funzione $ on. Per chiamare $ scope. $ Off (tipo, funzione) avremmo bisogno di mantenere un riferimento alla funzione anonima. Sta solo pensando in un modo diverso da come si farebbe normalmente per aggiungere / rimuovere listener di eventi in un linguaggio come ActionScript o il pattern Observable in Java
dannrob

60

Guardando la maggior parte delle risposte, sembrano eccessivamente complicate. Angular ha meccanismi incorporati per annullare la registrazione.

Utilizza la funzione di annullamento$on della registrazione restituita da :

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

Per quanto semplici, ci sono molte risposte ma questo è più conciso.
David Aguilar

8
Tecnicamente corretto, anche se un po 'fuorviante, perché $scope.$onnon è necessario annullare la registrazione manualmente su $destroy. Un esempio migliore sarebbe usare un file $rootScope.$on.
hgoebl

2
risposta migliore ma voglio vedere più spiegazioni sul motivo per cui chiamare quell'ascoltatore all'interno di $ destroy uccide l'ascoltatore.
Mohammad Rafigh

1
@ MohammadRafigh Chiamare l'ascoltatore all'interno di $ destroy è proprio dove ho scelto di metterlo. Se ricordo bene, questo era il codice che avevo all'interno di una direttiva e aveva senso che quando l'ambito delle direttive veniva distrutto, gli ascoltatori non sarebbero stati registrati.
long2know

@hgoebl Non so cosa intendi. Se, ad esempio, ho una direttiva che viene utilizzata in più posizioni e ciascuna registra un ascoltatore per un evento, in che modo l'utilizzo di $ rootScope. $ On mi aiuterebbe? Lo smaltimento del campo di applicazione della direttiva sembra essere il posto migliore per disporre dei suoi ascoltatori.
long2know

26

Questo codice funziona per me:

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

1
Guardare gli ascoltatori $ rootScope. $$ è anche un buon modo per osservare il ciclo di vita dell'ascoltatore e per sperimentarlo.
XML

Sembra semplice e fantastico. Penso che sia appena stato rimosso il riferimento alla funzione. non è vero?
Jay Shukla

27
Questa soluzione non è consigliata perché il membro $$ listener è considerato privato. In effetti, qualsiasi membro di un oggetto angolare con il prefisso "$$" è privato per convenzione.
shovavnik

5
Non consiglierei questa opzione, perché rimuove tutti gli ascoltatori, non solo quello che devi rimuovere. Potrebbe causare problemi in futuro quando si aggiunge un altro ascoltatore in un'altra parte dello script.
Rainer Plumer

10

EDIT: Il modo corretto per farlo è nella risposta di @ LiviuT!

Puoi sempre estendere l'ambito di Angular per consentirti di rimuovere tali ascoltatori in questo modo:

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

Ed ecco come funzionerebbe:

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

Ed eccone una parte in azione


Ha funzionato come un fascino! ma l'ho modificato un po 'l'ho messo nella sezione
.run

Amo questa soluzione. Rende una soluzione molto più pulita, molto più facile da leggere. +1
Rick

7

Dopo il debug del codice, ho creato la mia funzione proprio come la risposta di "blesh". Quindi questo è quello che ho fatto

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

quindi collegando la mia funzione a $ rootscope ora è disponibile per tutti i miei controller.

e nel mio codice sto facendo

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

Grazie

EDIT: Il modo AngularJS per farlo è nella risposta di @ LiviuT! Ma se vuoi annullare la registrazione dell'ascoltatore in un altro ambito e allo stesso tempo vuoi evitare di creare variabili locali per mantenere i riferimenti della funzione di annullamento della registrazione. Questa è una possibile soluzione.


1
In realtà sto cancellando la mia risposta, perché la risposta di @ LiviuT è corretta al 100%.
Ben Lesh

La risposta di @blesh LiviuT è corretta e in realtà un approccio angualar per l'annullamento della registrazione, ma non si collega bene per gli scenari in cui è necessario annullare la registrazione dell'ascoltatore in un ambito diverso. Quindi questa è un'alternativa facile.
Hitesh.Aneja

1
Fornisce lo stesso collegamento di qualsiasi altra soluzione. Metti semplicemente la variabile contenente la funzione di distruzione in una chiusura esterna o anche in una raccolta globale ... o ovunque tu voglia.
Ben Lesh

Non voglio continuare a creare variabili globali per mantenere i riferimenti delle funzioni di annullamento della registrazione e inoltre non vedo alcun problema con l'utilizzo della mia funzione $ off.
Hitesh.Aneja

1

La risposta di @ LiviuT è fantastica, ma sembra lasciare molte persone a chiedersi come riaccedere alla funzione di smontaggio del gestore da un altro $ scope o funzione, se si desidera distruggerla da un luogo diverso da dove è stata creata. La risposta di @ Рустем Мусабеков funziona benissimo, ma non è molto idiomatica. (E si basa su quello che dovrebbe essere un dettaglio di implementazione privato, che potrebbe cambiare in qualsiasi momento.) E da lì, diventa solo più complicato ...

Penso che la risposta facile qui sia semplicemente portare un riferimento alla funzione di abbattimento ( offCallMeFnnel suo esempio) nel gestore stesso, e poi chiamarlo in base a qualche condizione; forse un argomento che includi nell'evento che $ trasmetti o $ emetti. I gestori possono così abbattere se stessi, quando vuoi, dove vuoi, portando in giro i semi della loro stessa distruzione. Così:

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

Due pensieri:

  1. Questa è un'ottima formula per un gestore run-once. Lascia cadere i condizionali e corri selfDestructnon appena ha completato la sua missione suicida.
  2. Mi chiedo se l'ambito di origine verrà mai correttamente distrutto e raccolto in modo spazzatura, dato che stai portando riferimenti a variabili chiuse. Dovresti usarne un milione anche solo per avere un problema di memoria, ma sono curioso. Se qualcuno ha qualche intuizione, per favore condividi.

1

Registra un hook per annullare l'iscrizione ai tuoi listener quando il componente viene rimosso:

$scope.$on('$destroy', function () {
   delete $rootScope.$$listeners["youreventname"];
});  

Sebbene non sia il modo generalmente accettato per farlo, a volte questa è la soluzione necessaria.
Tony Brasunas

1

Nel caso in cui sia necessario attivare e disattivare più volte l'ascoltatore, è possibile creare una funzione con booleanparametro

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

0

'$ on' stesso restituisce la funzione per l'annullamento della registrazione

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

puoi chiamare la funzione unregister () per annullare la registrazione di quell'ascoltatore


0

Un modo è semplicemente distruggere l'ascoltatore una volta che hai finito.

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.