Jesse Jesse - 2 months ago 11x
AngularJS Question

UI Router: Keep url after $stateChangeError (while resolving dependencies)


1) We are in state A

2) Users clicks a link (target is state B)

3) $stateChangeStart

4) dependencies of state B get resolved, one throws an error

5) $stateChangeError

6) url is still of state A

Wanted behavior:

  • I want to open an modal dialog containing the error message

  • The Url should be the url of state B (so the user can send us the link or hit refresh to try again)

  • Ideally, the view of state A is still in the background of the modal

  • Going back in browser history should get us back to state A (the modal can listen to the popstate event and close itself)

  • closing the modal should also get us back to state A (so change the url again)

In general, to change the url without reloading the view I would do the following:

// in .config() block

// somewhere else
var preventNextSuccess = false;
var unbindError = $rootScope.$on('$stateChangeError', function () {
preventNextSuccess = true;
$location.path($state.href(toState, toParams));

$rootScope.$on('$locationChangeSuccess', function $event) {
if (preventNextSuccess) {
preventNextSuccess = false;
} else {


But in this case preventing the $locationChangeSuccess event does not help because the error appears after $locationStateStart and $stateChangeStart and before $locationChangeSuccess and $stateChangeSuccess.

So with this solution I get an infinite loop.
Preventing the start event does not help either because the url won't be set in that case.

Does anyone have an Idea how to achieve this? Thanks in advance :)


I found a way that works in our case:

$rootScope.$on('$stateChangeError', function ($event, toState, toParams, fromState, fromParams, response) {
    ErrorModalService.openErrorModal({error: error});
    var failedStateUrl = $state.href(toState, toParams);
    var fromStateUrl = $state.href(fromState, fromParams);

    if (fromStateUrl !== null && fromStateUrl !== failedStateUrl) {
          var unbindLocationChange = $rootScope.$on('$locationChangeStart', function ($event) {
          // After $stateChangeError, the url gets set back to the url of the previous state.
          // We want to keep the failedUrl (without reloading that state though)
          $timeout(function () {
             history.pushState(null, '', failedStateUrl);