I was wondering two things, in the context of angularJS event handling.
To provide an answer to #2 (because I think @Stewie's answer to #1 is a really good one), while I'd hesitate to ever offer conclusive rules that say, "if you see this, then it's bad code", I would offer to say that if you have two event handlers, and one can only execute after the other has run: you should evaluate why that is the case and if you couldn't better encapsulate or organize the way your logic executes.
One of the primary use cases of pub/sub event broadcasting/listening is to allow separate components that are fully independent of one another to operate on their domain of influence in an independent way asynchronously. By one handler having to operate only after another handler has run first you are removing the asynchronous nature of pub/sub by adding a secondary requirement (though possibly necessary).
If it's an absolutely necessary dependency, then no: it's not a symptom of bad design - its a symptom of the requirements of that feature.
It is a bit hacky and absolutely not recommended to use in your design, but sometimes you don't have a choice (been there so many times).
$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');
};
As you can see from the example, I have used setTimeout
to trick the order of running the actual handler and make the eventHandler1
handler to run last, although it has been called first.
To set the execution priority, just change the setTimeout
delay as necessary.
This is not ideal and is only suited for specific cases.
It's a bit messy (and I wouldn't recommend it) to go about this route, but wanted to provide an alternative incase you are unable to ensure serviceB will be initialized before serviceA and you absolutely need serviceB's listener to be executed first.
You can manipulate the $rootScope.$$listeners
array to put serviceB's listener first.
Something like this would work when adding the listener to $rootScope
on 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);
To add another comment on point #2, if you need to guarantee order you could implement an observer pattern using a service with an array of listeners. In the service you can define "addListener" functions that also defines how the listeners are ordered. You can then inject this service into whatever other components need to fire events.
Very good question.
Event handlers are executed in order of initialization.
I haven't really thought about this before, because my handlers never needed to know which one run first, but by the look of you fiddle I can see that the handlers are called in the same order in which they are initialized.
In you fiddle you have a controller controllerA
which depends on two services, ServiceA
and ServiceB
:
myModule
.controller('ControllerA',
[
'$scope',
'$rootScope',
'ServiceA',
'ServiceB',
function($scope, $rootScope, ServiceA, ServiceB) {...}
]
);
Both services and the controller define an event listener.
Now, all dependencies need to be resolved before being injected, which means that both services will be initialized before being injected into the controller. Thus, handlers defined in the services will be called first, because service factories are initialized before controller.
Then, you may also observe that the services are initialized in order they are injected. So ServiceA
is initialized before ServiceB
because they are injected in that order into the controller. If you changed their order inside the controller signature you'll see that their initilization order is also changed (ServiceB
comes before ServiceA
).
So, after the services are initialized, the controller gets initialized as well, and with it, the event handler defined within.
So, the end result is, on $broadcast, the handlers will be executed in this order: ServiceA
handler, ServiceB
handler, ControllerA
handler.
I'm a new user to Angular JS so please forgive if the answer is less than optimal. :P
If you require the order of the functions triggered by an event to be dependent (i.e., Function A then Function B) then might not creating a trigger function be better?
function trigger(event,data) {
FunctionA(event,data);
FunctionB(event,data);
}
$rootScope.on('eventTrigger',trigger);