Comment se désinscrire d'un événement diffusé dans angularJS. Comment supprimer une fonction enregistrée via $ on

278

J'ai inscrit mon auditeur à un événement de diffusion $ à l'aide de la fonction $ on

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

et je souhaite désinscrire cet écouteur en fonction d'une règle métier particulière. Mais mon problème est qu'une fois enregistré, je ne peux plus le désinscrire.

Existe-t-il une méthode dans AngularJS pour désinscrire un auditeur particulier? Une méthode comme $ on qui désenregistre cet événement, peut être $ off. Donc, sur la base de la logique métier, je peux dire

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

et cette fonction cesse d'être appelée lorsque quelqu'un diffuse l'événement "onViewUpdated".

Merci

EDIT : Je veux désinscrire l'auditeur d'une autre fonction. Pas la fonction où je l'enregistre.

Hitesh.Aneja
la source
2
Pour tous ceux qui se demandent, la fonction retournée est documentée ici
Fagner Brack

Réponses:

477

Vous devez stocker la fonction retournée et l'appeler pour vous désinscrire de l'événement.

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

Cela se trouve dans le code source :) au moins dans 1.0.4. Je vais juste poster le code complet car c'est court

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

Consultez également la documentation .

Liviu T.
la source
Oui. Après avoir débogué le code sorce, j'ai découvert qu'il existe un tableau d'écouteurs $$ qui contient tous les événements et j'ai créé ma fonction $ off. Merci
Hitesh.Aneja
Quel est le cas d'utilisation réel que vous ne pouvez pas utiliser la méthode angulaire de désenregistrement fournie? La désinscription effectuée dans une autre étendue n'est-elle pas liée à l'étendue qui a créé l'écouteur?
Liviu T.
1
Oui, j'ai supprimé ma réponse parce que je ne veux pas dérouter les gens. C'est la bonne façon de procéder.
Ben Lesh
3
@Liviu: Cela deviendra un casse-tête avec une application croissante. Ce n'est pas seulement cet événement, il y a aussi beaucoup d'autres événements et pas nécessairement que je me désinscris toujours dans la même fonction de portée. Il peut y avoir des cas où j'appelle une fonction qui enregistre cet écouteur mais désenregistre l'écouteur sur un autre appel, même dans ces cas, je n'obtiendrai pas la référence à moins que je ne les stocke en dehors de ma portée. Donc, pour ma mise en œuvre actuelle, ma mise en œuvre semble une solution viable pour moi. Mais j'aimerais certainement connaître les raisons pour lesquelles AngularJS l'a fait de cette manière.
Hitesh.Aneja
2
Je pense qu'Angular l'a fait de cette façon parce que la plupart du temps, les fonctions anonymes en ligne sont utilisées comme arguments de la fonction $ on. Pour appeler $ scope. $ Off (type, fonction), nous devons conserver une référence à la fonction anonyme. Il s'agit simplement de penser différemment à la façon dont on ajouterait / supprimerait normalement des écouteurs d'événements dans un langage comme ActionScript ou le modèle observable en Java
dannrob
60

En regardant la plupart des réponses, elles semblent trop compliquées. Angular a intégré des mécanismes de désinscription.

Utilisez la fonction de désinscription renvoyée par$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
la source
Aussi simple que cela, il y a beaucoup de réponses mais c'est plus concis.
David Aguilar
8
Techniquement correct, mais un peu trompeur, car il $scope.$onn'est pas nécessaire de l'enregistrer manuellement $destroy. Un meilleur exemple serait d'utiliser a $rootScope.$on.
hgoebl le
2
meilleure réponse mais voulez voir plus d'explications sur pourquoi appeler cet écouteur dans $ destroy tue l'auditeur.
Mohammad Rafigh
1
@MohammadRafigh Appeler l'auditeur à l'intérieur de $ destroy est juste là où j'ai choisi de le mettre. Si je me souviens bien, c'était du code que j'avais à l'intérieur d'une directive et il était logique que lorsque la portée des directives était détruite, les écouteurs ne devaient pas être enregistrés.
long2know
@hgoebl Je ne sais pas ce que tu veux dire. Si j'ai, par exemple, une directive qui est utilisée à plusieurs endroits et que chacun enregistre un écouteur pour un événement, comment l'aide de $ rootScope. $ Sur m'aiderait-elle? L'élimination du champ d'application de la directive semble être le meilleur endroit pour disposer de ses auditeurs.
long2know
26

Ce code fonctionne pour moi:

$rootScope.$$listeners.nameOfYourEvent=[];
Rustem Mussabekov
la source
1
En regardant $ rootScope. $$, les écouteurs sont également un bon moyen d'observer le cycle de vie de l'auditeur et de l'expérimenter.
XML
Semble simple et génial. Je pense que sa référence de fonction vient d'être supprimée. n'est-ce pas?
Jay Shukla
26
Cette solution n'est pas recommandée car le membre $$ listeners est considéré comme privé. En fait, tout membre d'un objet angulaire avec le préfixe «$$» est privé par convention.
shovavnik
5
Je ne recommanderais pas cette option, car elle supprime tous les écouteurs, pas seulement celui que vous devez supprimer. Cela peut entraîner des problèmes à l'avenir lorsque vous ajoutez un autre écouteur dans une autre partie du script.
Rainer Plumer
10

EDIT: La bonne façon de le faire est dans la réponse de @ LiviuT!

Vous pouvez toujours étendre la portée d'Angular pour vous permettre de supprimer de tels écouteurs comme ceci:

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

Et voici comment cela fonctionnerait:

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

Et voici un plongeur en action

Ben Lesh
la source
A fonctionné comme un charme! mais je l'ai un peu édité je l'ai mis dans la section
.run
J'adore cette solution. En fait une solution beaucoup plus propre - tellement plus facile à lire. +1
Rick
7

Après avoir débogué le code, j'ai créé ma propre fonction, tout comme la réponse de "blesh". C'est donc ce que j'ai fait

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

donc en attachant ma fonction à $ rootscope maintenant elle est disponible pour tous mes contrôleurs.

et dans mon code je fais

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

Merci

EDIT: La manière AngularJS de le faire est dans la réponse de @ LiviuT! Mais si vous souhaitez désenregistrer l'écouteur dans une autre étendue et en même temps, ne pas créer de variables locales pour conserver les références de la fonction de désenregistrement. C'est une solution possible.

Hitesh.Aneja
la source
1
Je suis en train de supprimer ma réponse, car la réponse de @ LiviuT est 100% correcte.
Ben Lesh
La réponse de @blesh LiviuT est correcte et en fait une approche angulaire fournie pour le désenregistrement, mais ne se connecte pas bien pour les scénarios où vous devez désenregistrer l'auditeur dans une portée différente. C'est donc une alternative facile.
Hitesh.Aneja
1
Il offre le même raccordement que toute autre solution. Vous mettriez simplement la variable contenant la fonction de destruction dans une fermeture extérieure ou même dans une collection globale ... ou n'importe où vous voulez.
Ben Lesh
Je ne veux pas continuer à créer des variables globales pour conserver les références des fonctions de désenregistrement et je ne vois aucun problème avec l'utilisation de ma propre fonction $ off.
Hitesh.Aneja
1

@ La réponse de LiviuT est impressionnante, mais semble laisser beaucoup de gens se demander comment accéder à nouveau à la fonction de suppression du gestionnaire à partir d'une autre portée ou fonction $, si vous souhaitez la détruire d'un endroit autre que celui où elle a été créée. La réponse de @ Рустем Мусабеков fonctionne très bien, mais n'est pas très idiomatique. (Et repose sur ce qui est censé être un détail d'implémentation privé, qui pourrait changer à tout moment.) Et à partir de là, cela devient juste plus compliqué ...

Je pense que la réponse facile ici est de simplement porter une référence à la fonction de démontage ( offCallMeFndans son exemple) dans le gestionnaire lui-même, puis de l'appeler en fonction d'une condition; peut-être un argument que vous incluez sur l'événement que vous diffusez ou émettez. Les manipulateurs peuvent ainsi se détruire, quand vous voulez, où vous voulez, emportant avec vous les graines de leur propre destruction. Ainsi:

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

Deux réflexions:

  1. Il s'agit d'une excellente formule pour un gestionnaire à exécution unique. Il suffit de laisser tomber les conditionnels et de courir selfDestructdès qu'il a terminé sa mission suicide.
  2. Je me demande si la portée d'origine sera jamais correctement détruite et récupérée, étant donné que vous portez des références à des variables fermées. Il faudrait en utiliser un million pour que ce soit un problème de mémoire, mais je suis curieux. Si quelqu'un a des idées, veuillez les partager.
XML
la source
1

Enregistrez un hook pour désinscrire vos écouteurs lorsque le composant est supprimé:

$scope.$on('$destroy', function () {
   delete $rootScope.$$listeners["youreventname"];
});  
Dima
la source
Bien que ce ne soit pas la manière généralement acceptée de le faire, il s'agit parfois de la solution nécessaire.
Tony Brasunas
1

Dans le cas où vous devez activer et désactiver l'auditeur plusieurs fois, vous pouvez créer une fonction avec un booleanparamètre

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

'$ on' lui-même renvoie la fonction pour désinscrire

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

vous pouvez appeler la fonction unregister () pour désenregistrer cet écouteur

Rajiv
la source
0

Une façon consiste simplement à détruire l'auditeur une fois que vous en avez terminé.

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