This was tested specifically with AngularJS 1.2.4 and AngularUI Router 0.2.5.
If the user clicks on a link created with ui-sref, or the state is changed with $state
- $stateChangeStart will precede $locationChangeStart.
- Calling event.preventDefault() in a $stateChangeStart listener will prevent the $locationChangeStart event, and will prevent the page change completely.
- Calling event.preventDefault() in a $locationChangeStart listener without doing the same in a $stateChangeStart listener will prevent the URL from changing but will change the state (content), leaving your app in a bad situation.
If instead the user clicks on a link created with href, or types a URL into the browser, or the location is changed using $location
- $locationChangeStart will precede $stateChangeStart.
- Calling event.preventDefault() in a $locationChangeStart listener will prevent the $stateChangeStart event, and will prevent the page change completely.
- Calling event.preventDefault() in a $stateChangeStart listener without doing the same in a $locationChangeStart listener will prevent the state (content) from changing, but the URL will change, leaving your app in a bad situation.
This difference makes sense in a way. If you trigger a state change, it goes through the UI Router, and then the UI Router triggers a location change. If you trigger a location change through $location, that's part of Angular itself, so UI Router will learn of the event after Angular. Regardless, this makes things tricky if you want your app to respond properly in both scenarios.
The solution
Synchronizing logic within both event listeners would be extremely difficult, if not impossible, to get right. My solution is to switch all my ui-sref links to be normal href links, and when I want to trigger a page change programmatically, I do it through $location, never through $state. That way every page change goes through Angular first, and the $locationChangeStart (and $locationChangeSuccess) is all I have to worry about.
I still use UI Router for its nested views, which I could not do without.
One more thing - the first visit
Keep in mind that in the case of a user typing a URL into the browser, the $locationChangeStart and $stateChangeStart event listeners will only be called if the user already has the site loaded. If it is the user's first visit to the site in that browser session, the listeners will not be called because they will not have been registered when the change started. You'll need another way to keep a user out of certain pages in that case. My solution of choice is to redirect the user from within the resolve block of the controller of each page that a non-logged-in user should not be able to access.