在angularJS事件处理的上下文中,我想知道两件事。
- 如何定义侦听同一事件的处理程序的触发顺序
- 如果你开始对此感到疑惑,这是一个糟糕设计的标志吗
在阅读了关于angular$on、$broadcast和$emit以及本地DOM事件流的文档后,我想我了解了在不同范围内触发事件处理程序的顺序。问题是当多个处理程序在同一范围内(例如$rootScope)从不同的位置侦听时(例如Controllers vs Services)。
为了说明这个问题,我将一个jsfiddle与一个控制器和两个服务组合在一起,所有这些都通过$rootScope进行通信http://jsfiddle.net/Z84tX/
Thanks
非常好的问题。
事件处理程序按初始化顺序执行
我以前并没有真正考虑过这一点,因为我的处理程序从不需要知道哪一个先运行,但通过你的摆弄,我可以看到处理程序的调用顺序与它们初始化的顺序相同。
在你的小提琴中,你有一个控制器controllerA
,它依赖于两个服务,ServiceA
和ServiceB
:
myModule
.controller('ControllerA',
[
'$scope',
'$rootScope',
'ServiceA',
'ServiceB',
function($scope, $rootScope, ServiceA, ServiceB) {...}
]
);
服务和控制器都定义了一个事件侦听器。
现在,所有依赖项都需要在注入之前进行解析,这意味着两个服务都将在注入控制器之前进行初始化。因此,服务中定义的处理程序将首先被调用,因为服务工厂是在控制器之前初始化的。
然后,您还可以观察到服务是按照注入的顺序初始化的。因此ServiceA
在ServiceB
之前被初始化,因为它们按顺序被注入控制器中。如果您在控制器签名中更改了它们的顺序,您将看到它们的初始化顺序也发生了更改(ServiceB
位于ServiceA
之前)。
因此,在初始化服务之后,控制器也将初始化,并随之初始化中定义的事件处理程序。
因此,最终的结果是,在$broadcast上,处理程序将按以下顺序执行:ServiceA
处理程序、ServiceB
处理程序和ControllerA
处理程序。
使用这条路线有点麻烦(我不建议这样做),但希望提供一种替代方案,以防您无法确保在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);
为了提供对#2的答案(因为我认为@Stewie对#1的答案真的很好),虽然我不愿意提供决定性的规则,比如"如果你看到了这个,那就是坏代码",但我愿意说,如果你有两个事件处理程序,其中一个只能在另一个运行后执行:您应该评估为什么会出现这种情况,以及您是否不能更好地封装或组织您的逻辑执行方式。
发布/订阅事件广播/侦听的主要用例之一是允许彼此完全独立的独立组件以独立的方式异步地在其影响域上运行。通过一个处理程序必须在另一个处理进程首先运行后才进行操作,您可以通过添加次要需求(尽管可能是必要的)来消除pub/sub的异步特性。
如果它是一个绝对必要的依赖项,那么不:这不是糟糕设计的症状,而是该功能需求的症状。
我是Angular JS的新用户,如果答案不太理想,请原谅。:P
如果您要求由事件触发的函数的顺序是依赖的(即,函数A然后是函数B),那么不创建触发函数可能会更好吗?
function trigger(event,data) {
FunctionA(event,data);
FunctionB(event,data);
}
$rootScope.on('eventTrigger',trigger);
要在第2点上添加另一条注释,如果需要保证顺序,可以使用带有侦听器阵列的服务来实现观察者模式。在服务中,您可以定义"addListener"函数,这些函数还定义了侦听器的排序方式。然后,您可以将此服务注入到激发事件所需的任何其他组件中。
它有点过时,绝对不建议在设计中使用,但有时你别无选择(已经使用过很多次了)。
$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
延迟即可。
这并不理想,只适用于特定情况。