285 votes

Comment se désabonner à un événement de diffusion en angularJS. Comment supprimer une fonction enregistrée via $on

J'ai enregistré mon écouteur à un événement $broadcast en utilisant la fonction $on.

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

et je veux désenregistrer cet écouteur en fonction d'une règle de gestion particulière. Mais mon problème est qu'une fois qu'il est enregistré, je ne suis pas en mesure de le désenregistrer.

Existe-t-il une méthode dans AngularJS pour désenregistrer un écouteur particulier ? Une méthode comme $on qui désenregistre cet événement, ou $off. Ainsi, sur la base de la logique commerciale, je peux dire que

 $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ésenregistrer le listener d'une autre fonction. Pas la fonction où je l'ai enregistré.

3 votes

Pour ceux qui se posent la question, la fonction retournée est documentée ici

488voto

Liviu T. Points 8894

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 la version 1.0.4. Je vais juste poster le code complet puisqu'il 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;
    };
},

Voir aussi les docs .

0 votes

Oui. Après avoir débogué le code sorcier, j'ai découvert qu'il existe un tableau $$listeners qui contient tous les événements et j'ai créé ma fonction $off. Merci

0 votes

Quel est le cas d'utilisation réel pour lequel vous ne pouvez pas utiliser la méthode angulaire de désenregistrement ? Le désenregistrement se fait-il dans une autre portée non liée à la portée qui a créé le listener ?

1 votes

Oui, j'ai en fait supprimé ma réponse parce que je ne veux pas embrouiller les gens. C'est la bonne façon de faire.

26voto

Ce code fonctionne pour moi :

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

1 votes

L'examen de $rootScope.$$listeners est également un bon moyen d'observer le cycle de vie de l'écouteur, et de l'expérimenter.

0 votes

Ça a l'air simple et génial. Je pense que c'est juste une référence supprimée de la fonction, n'est-ce pas ?

27 votes

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.

10voto

Ben Lesh Points 39290

EDIT : La façon correcte de procéder se trouve dans la réponse de @LiviuT !

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

//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 une photo de l'appareil en action.

0 votes

Ça a marché comme sur des roulettes ! Mais je l'ai modifié un peu en le mettant dans la section .run.

0 votes

J'adore cette solution. C'est une solution beaucoup plus propre et beaucoup plus facile à lire. +1

7voto

Hitesh.Aneja Points 485

Après avoir débogué le code, j'ai créé ma propre fonction comme la réponse de "blesh". Voici 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);
                    }
                }
            }
        }
});

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

et dans mon code, je fais

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

Merci

EDIT : La façon AngularJS de le faire est dans la réponse de @LiviuT ! Mais si vous voulez désenregistrer l'écouteur dans une autre portée et en même temps vous voulez rester loin de la création de variables locales pour garder les références de la fonction de désenregistrement. Voici une solution possible.

1 votes

En fait, je supprime ma réponse, car la réponse de @LiviuT est 100% correcte.

0 votes

La réponse de @blesh LiviuT est correcte et constitue en fait une approche angulaire de désenregistrement, mais elle n'est pas adaptée aux scénarios dans lesquels vous devez désenregistrer l'écouteur dans une portée différente. C'est donc une alternative facile.

1 votes

Il fournit la même connexion que toute autre solution. Il suffit de placer la variable contenant la fonction de destruction dans une fermeture extérieure ou même dans une collection globale... ou où vous voulez.

1voto

XMLilley Points 2351

La réponse de @LiviuT est géniale, mais semble laisser beaucoup de gens se demander comment accéder à nouveau à la fonction de destruction du gestionnaire à partir d'un autre $scope ou d'une autre fonction, si vous voulez le détruire à partir d'un endroit autre que celui où il a été créé. La réponse de @ fonctionne très bien, mais n'est pas très idiomatique. (Et s'appuie sur ce qui est censé être un détail d'implémentation privé, qui pourrait changer à tout moment). Et à partir de là, ça devient de plus en plus compliqué...

Je pense que la réponse facile ici est de simplement porter une référence à la fonction de démolition ( offCallMeFn dans son exemple) dans le gestionnaire lui-même, puis l'appeler en fonction d'une condition ; peut-être une arg que vous incluez dans l'événement que vous $broadcast ou $emit. Les gestionnaires peuvent ainsi se détruire eux-mêmes, quand vous le voulez, où vous le voulez, en transportant les graines de leur propre destruction. Par exemple :

// 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 pensées :

  1. C'est une excellente formule pour un handler de type run-once. Il suffit de laisser tomber les conditionnels et de lancer selfDestruct dès qu'il aura terminé sa mission suicide.
  2. Je me demande si la portée d'origine sera jamais correctement détruite et collectée, étant donné que vous transportez 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 une idée, merci de la partager.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X