AngularJS $on事件处理程序触发顺序

25

在AngularJS事件处理的上下文中,我想知道两件事:

  1. 如何定义处理同一事件的处理程序触发的顺序?
  2. 如果您开始思考这个问题,是否说明设计有问题?

阅读了有关angular $on、$broadcast和$emit以及本地DOM事件流的文档后,我认为我理解了不同作用域中事件处理程序将按照哪种顺序触发的方式。问题在于,当几个处理程序在同一个作用域(例如$rootScope)中从各个位置(例如Controllers和Services)侦听时。

为了说明问题,我组合了一个jsfiddle,其中包含一个控制器和两个服务,它们都通过$rootScope进行通信:http://jsfiddle.net/Z84tX/

谢谢

6个回答

26

非常好的问题。

事件处理程序按初始化顺序执行。

以前我没有真正考虑过这个问题,因为我的处理程序从来不需要知道哪一个先运行,但是从你的fiddle看来,处理程序按照它们初始化的顺序调用。

在你的fiddle中,你有一个控制器controllerA,它依赖于两个服务:ServiceAServiceB

myModule
  .controller('ControllerA', 
    [
      '$scope', 
      '$rootScope', 
      'ServiceA', 
      'ServiceB', 
      function($scope, $rootScope, ServiceA, ServiceB) {...}
    ]
  );

服务和控制器都定义了一个事件监听器。

现在,所有依赖项在注入之前都需要解决,这意味着两个服务将在注入到控制器之前进行初始化。因此,服务中定义的处理程序将首先被调用,因为服务工厂在控制器之前初始化。

然后,您可能还会注意到,服务按照它们被注入的顺序进行初始化。所以ServiceAServiceB之前初始化,因为它们按照这个顺序被注入到控制器中。如果您更改了它们在控制器签名中的顺序,您会发现它们的初始化顺序也会改变(ServiceBServiceA之前)。

因此,在服务初始化之后,控制器也会被初始化,并且其中定义的事件处理程序也会随之初始化。

因此,最终结果是,在$broadcast时,处理程序将按照此顺序执行:ServiceA处理程序,ServiceB处理程序,ControllerA处理程序。


3
为了回答问题#2(因为我认为@Stewie对问题#1的回答非常好),虽然我不愿提供决定性规则来说明“如果你看到这个,那么它是糟糕的代码”,但我会建议说,如果你有两个事件处理程序,其中一个只能在另一个运行后才能执行:你应该评估为什么会出现这种情况,以及是否可以更好地封装或组织逻辑执行方式。
发布/订阅事件广播/监听的主要用例之一是允许完全独立于彼此的单独组件异步地操作其影响域。通过一个处理程序必须在另一个处理程序之后运行,您正在删除pub/sub的异步性质,并添加了一个次要要求(尽管可能是必要的)。
如果这是绝对必要的依赖关系,那么不,它不是糟糕设计的症状 - 它是该功能要求的症状。

3

如果您不能确保serviceA在serviceB之前初始化,但又绝对需要先执行serviceB的监听器,则可以采用以下方法(虽然有些混乱,不建议这样做)。您可以操作 $rootScope.$$listeners 数组,将serviceB的监听器放在第一位。

在serviceB上向 $rootScope 添加监听器时,可以采用以下方式:

var listener, listenersArray;
$rootScope.$on('$stateChangeStart', someFunction);
listenersArray = $rootScope.$$listeners.$stateChangeStart;
listener = listenersArray[listenersArray.length - 1];
listenersArray.splice(listenersArray.length - 1, 1);
listenersArray.unshift(listener);

尽管这是一种不太正规的方法,但有时候没有其他办法可以改变监听器的顺序,特别是当其他监听器是由第三方代码添加的时候。 - Klaster_1 Нет войне
不错。如果在附加监听器时能够指定某种优先级,那就更好了。 - cipak
等价于: listenersArray.unshift(listenersArray.splice(-1, 1)[0]); - JohnLock
甚至更简单:listenersArray.unshift(listenersArray.pop()); - p0stm

1

我是一个新的Angular JS用户,如果答案不够优秀,请见谅。:P

如果您需要事件触发的函数顺序有依赖性(即先执行函数A再执行函数B),那么创建一个触发函数可能会更好。

function trigger(event,data) {
    FunctionA(event,data);
    FunctionB(event,data);
}

$rootScope.on('eventTrigger',trigger);

如何触发事件? - fdrv

0
补充一下第二点,如果你需要保证顺序,可以使用观察者模式来实现一个带有监听器数组的服务。在该服务中,您可以定义“addListener”函数,该函数还定义了监听器的排序方式。然后,您可以将此服务注入到需要触发事件的任何其他组件中。

0

这是一种有点巧妙的方法,绝对不建议在设计中使用,但有时你别无选择(我也遇到过很多次)。

$rootScope.$on('someEvent', () => {
    setTimeout(eventHandler1, 1);
});

$rootScope.$on('someEvent', eventHandler2);

const eventHandler1 = () => {
    console.log('This one runs last');
};

const eventHandler2 = () => {
    console.log('This one runs first');
};

从示例中可以看出,我使用了setTimeout来欺骗运行实际处理程序的顺序,并使eventHandler1处理程序最后运行,尽管它是首先被调用的。

要设置执行优先级,请根据需要更改setTimeout延迟。

这并不理想,只适用于特定情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接