25

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

  1. 如何定义监听同一事件的处理程序的触发顺序?
  2. 如果您开始对此感到疑惑,这是否表明设计不佳?

在阅读了有关 angular $on、$broadcast 和 $emit以及本机DOM 事件流的文档后,我想我了解在不同范围内触发事件处理程序的顺序。问题是当多个处理程序从不同的地方(例如控制器与服务)在同一范围内(例如 $rootScope)进行侦听时。

为了说明这个问题,我将一个带有一个控制器和两个服务的 jsfiddle 放在一起,所有这些都通过 $rootScope http://jsfiddle.net/Z84tX/进行通信

Thanks

4

6 回答 6

26

非常好的问题。

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

我之前并没有真正考虑过这一点,因为我的处理程序从不需要知道哪个先运行,但是从你小提琴的外观来看,我可以看到处理程序的调用顺序与它们初始化的顺序相同。

在您的小提琴中,您有一个controllerA依赖于两个服务的控制器,ServiceA并且ServiceB

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

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

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

然后,您可能还会观察到服务按照注入的顺序进行了初始化。SoServiceA之前被初始化,ServiceB因为它们按顺序注入到控制器中。如果您在控制器签名中更改了它们的顺序,您会看到它们的初始化顺序也发生了变化(ServiceB在之前ServiceA)。

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

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

于 2013-07-03T15:29:12.550 回答
3

走这条路线有点混乱(我不推荐),但想提供一个替代方案,以防您无法确保 serviceB 将在 serviceA 之前初始化,并且您绝对需要首先执行 serviceB 的侦听器。

您可以操作$rootScope.$$listeners数组以将 serviceB 的侦听器放在首位。

$rootScope在 serviceB 上添加侦听器时,这样的事情会起作用:

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);
于 2015-08-07T21:31:49.013 回答
3

为了提供#2 的答案(因为我认为@Stewie 对#1 的回答非常好),虽然我会犹豫是否提供结论性规则,说“如果你看到这个,那么它就是糟糕的代码”,我如果你有两个事件处理程序,并且一个只能在另一个运行后执行:你应该评估为什么会这样,如果你不能更好地封装或组织你的逻辑执行方式

发布/订阅事件广播/侦听的主要用例之一是允许完全相互独立的独立组件以一种独立的方式异步地在其影响域上运行。通过一个处理程序必须在另一个处理程序首先运行后才进行操作,您通过添加次要要求(尽管可能是必要的)来消除 pub/sub 的异步性质。

如果它是绝对必要的依赖,那么不:它不是糟糕设计的症状 - 它是该功能要求的症状

于 2015-08-28T14:54:57.317 回答
1

我是 Angular JS 的新用户,所以如果答案不是最佳的,请原谅。:P

如果您要求事件触发的函数的顺序是依赖的(即,函数 A 然后是函数 B),那么创建触发器函数可能会更好吗?

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

$rootScope.on('eventTrigger',trigger);
于 2013-08-22T04:12:16.243 回答
0

要在第 2 点上添加另一条评论,如果您需要保证顺序,您可以使用具有一组侦听器的服务来实现观察者模式。在服务中,您可以定义“addListener”函数,该函数还定义了侦听器的排序方式。然后,您可以将此服务注入到需要触发事件的任何其他组件中。

于 2015-12-03T17:54:16.940 回答
0

它有点 hacky,绝对不建议在您的设计中使用,但有时您别无选择(去过那里很多次)。

$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根据需要更改延迟即可。

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

于 2020-11-15T15:51:28.970 回答