如何取消订阅angularJS中的广播事件。如何删除通过$ on注册的功能


278

我已经使用$ on函数将监听器注册到$ broadcast事件中

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

我想根据特定的业务规则取消注册此侦听器。但是我的问题是,一旦注册,我将无法注销。

AngularJS中是否有任何方法可以取消注册特定的侦听器?诸如$ on之类的方法可以取消注册此事件,可能是$ off。因此,基于业务逻辑,我可以说

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

并且当有人广播“ onViewUpdated”事件时,该函数将停止调用。

谢谢

编辑:我想注销另一个功能的侦听器。不是我注册的功能。


2
对于任何想知道的人,这里
Fagner Brack

Answers:


477

您需要存储返回的函数并调用它以取消订阅该事件。

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

至少在1.0.4中可以在源代码中找到它:)。我将发布完整的代码,因为它很短

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

另外,请参阅docs


是。在调试了源代码之后,我发现有一个包含所有事件的$$ listeners数组,并创建了我的$ off函数。谢谢
Hitesh.Aneja

您不能使用提供的角度注销方式的实际用例是什么?取消注册是在另一个作用域中完成的,而不是与创建侦听器的作用域相关联吗?
Liviu T.

1
是的,我实际上已经删除了答案,因为我不想让人们感到困惑。这是执行此操作的正确方法。
Ben Lesh 2013年

3
@Liviu:随着应用程序的增加,这将变得令人头疼。不仅此事件还有很多其他事件,而且不一定总是在同一作用域函数中注销。在某些情况下,当我调用正在注册此侦听器但在其他调用上注销该侦听器的函数时,即使在这些情况下,除非将它们存储在我的范围之外,否则我将无法获得引用。因此,对于我当前的实现,我的实现对我来说似乎是可行的解决方案。但绝对想知道AngularJS这样做的原因。
Hitesh.Aneja

2
我认为Angular这样做是因为很多时候,内联匿名函数被用作$ on函数的参数。为了调用$ scope。$ off(type,function),我们需要保留对匿名函数的引用。它只是以一种不同的方式思考与通常如何使用ActionScript或Java中的Observable模式之类的语言添加/删除事件侦听器
dannrob,2014年

60

查看大多数答复,它们似乎过于复杂。Angular内置了注销机制。

使用以下命令返回$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();
});

如此简单,答案很多,但这更加简洁。
David Aguilar

8
从技术上讲是正确的,尽管有点误导,因为$scope.$on不必在上手动注销$destroy。更好的例子是使用$rootScope.$on
hgoebl '16

2
最佳答案,但想了解有关为什么在$ destroy中调用该侦听器会杀死该侦听器的更多解释。
Mohammad Rafigh

1
@MohammadRafigh在$ destroy内调用侦听器正是我选择放置它的地方。如果我没记错的话,这是我在指令内部的代码,并且有道理的是,当销毁指令范围时,应该注销侦听器。
long2know

@hgoebl我不明白你的意思。例如,如果我有一个在多个地方使用的指令,并且每个指令都注册一个事件的侦听器,那么使用$ rootScope。$ on会如何帮助我?该指令的范围处置似乎是处置其侦听器的最佳场所。
long2know

26

该代码对我有用:

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

1
查看$ rootScope。$$ listeners也是观察收听者生命周期并进行试验的好方法。
XML

看起来很简单。我认为它只是删除了功能参考。是不是
杰·舒克拉2014年

26
不建议使用此解决方案,因为$$ listeners成员被视为私有成员。实际上,按惯例,前缀为“ $$”的角度对象的任何成员都是私有的。
shovavnik 2014年

5
我不建议使用此选项,因为它会删除所有侦听器,而不仅仅是您需要删除的那个侦听器。将来在脚本的另一部分中添加另一个侦听器时,可能会导致问题。
Rainer Plumer

10

编辑:正确的方法是在@LiviuT的答案!

您可以始终扩展Angular的范围,以允许您删除此类监听器,如下所示:

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

这是它的工作方式:

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

这是一个实用的工具


像魅力一样工作!但是我对其进行了一些编辑,然后将其放在.run部分中
aimiliano

喜欢这个解决方案。提供了一种更加干净的解决方案-更加易于阅读。+1
Rick

7

调试代码后,我创建了自己的函数,就像“ blesh”的答案一样。这就是我所做的

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

因此,通过将我的函数附加到$ rootscope,它现在可用于我的所有控制器。

在我的代码中

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

谢谢

编辑:AngularJS的方法是在@LiviuT的答案!但是,如果要在另一个作用域中注销侦听器,同时又要避免创建局部变量以保留注销功能的引用。这是一个可能的解决方案。


1
我实际上正在删除答案,因为@LiviuT的答案是100%正确的。
Ben Lesh

@blesh LiviuT的答案是正确的,并且通过提示提供了一种预先提供的方法来注销,但是对于必须在不同范围内注销侦听器的场景而言,效果并不理想。因此,这是一个简单的选择。
Hitesh.Aneja

1
它提供了与其他解决方案相同的功能。您只需将包含破坏函数的变量放在外部闭包中,甚至放在全局集合中……或您想要的任何位置。
Ben Lesh

我不想继续创建全局变量来保留注销功能的引用,并且我也看不到使用自己的$ off函数的任何问题。
Hitesh.Aneja

1

@LiviuT的答案很棒,但似乎很多人想知道如何从另一个$ scope或函数中重新访问该处理程序的拆解函数,如果您想从创建它的地方之外销毁它的话。@РустемМусабеков的答案很好,但不是很惯用。(并且依赖于可能是随时更改的私有实现细节。)从那里开始,它变得更加复杂...

我认为这里的简单答案是offCallMeFn在处理程序本身中简单地携带一个对拆解函数的引用(在他的示例中),然后根据某种条件对其进行调用。可能包括在$ broadcast或$ emit事件中的arg。因此,处理人员可以随时随地拆除自己,破坏自己的种子。像这样:

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

两个想法:

  1. 对于一次运行的处理程序来说,这是一个很好的公式。只要放弃条件,并在selfDestruct自杀任务完成后立即运行。
  2. 我想知道,如果您携带对闭包变量的引用,则原始范围是否会被正确销毁并进行垃圾回收。您甚至必须使用其中的一百万个内存问题,但是我很好奇。如果有人有见识,请分享。

1

注册一个挂钩,以在删除组件时取消订阅您的侦听器:

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

尽管这不是普遍接受的方法,但有时这是必要的解决方案。
托尼·布拉索纳斯

1

如果需要多次打开和关闭侦听器,则可以使用boolean参数创建一个函数

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

0

'$ on'本身返回注销功能

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

您可以调用unregister()函数来注销该侦听器


0

一种方法是在完成侦听器后就将其销毁。

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.