AngularJS $on Event Handler Trigger Order


Answer :

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.


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


Comments

Popular posts from this blog

Are Regular VACUUM ANALYZE Still Recommended Under 9.1?

Can Feynman Diagrams Be Used To Represent Any Perturbation Theory?