Skip to content

Infinite loop when redirecting to state from $stateChangeError handler #898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
albv opened this issue Feb 18, 2014 · 14 comments
Closed

Infinite loop when redirecting to state from $stateChangeError handler #898

albv opened this issue Feb 18, 2014 · 14 comments

Comments

@albv
Copy link

albv commented Feb 18, 2014

Here is a minimal example I made to reproduce my issue:

angular.module('routerTestApp', ['ui.router'])

    .config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {

        $urlRouterProvider.otherwise('/state/a');

        $stateProvider.
            state('state', {
                url: '/state',
                abstract: true,
                resolve: {
                    parentResolved: ['$timeout', function ($timeout) {
                        return $timeout(function () {
                            return [];
                        }, 1000);
                    }]
                }
            }).

            state('state.a', {
                url: '/a',
                resolve: {
                    resolved: ['$q', 'parentResolved', function ($q, parentResolved) {
                        return $q.reject({ redirect: 'state.b' });
                    }]
                }
            }).

            state('state.b', {
                url: '/b'
            });

    }])

    .run(['$rootScope', '$exceptionHandler', '$state', function ($rootScope, $exceptionHandler, $state) {
        $rootScope.$on('$stateChangeError', function (e, toState, toParams, fromState, fromParams, error) {
            if (error && error.redirect) {
                return $state.go(error.redirect);
            }

            $exceptionHandler(error);
        });
    }]);

The problem is when going to any non-existent url such as '/abc' for example - app falls to infinite 'location change' loop. Url should be pasted directly to browser address string because if previously we entered some valid state all subsequent redirects would work as expected.

By 'location change' loop I mean following:

  1. $urlRouterProvider.otherwise('/state/a') send us to 'state.a'
  2. 'state.a' resolve failed so $stateChangeError handler is called
  3. handler redirects us to 'state.b'
  4. but syncUrl() function that called in ui-router after firing $stateChangeError sees that currentLocation (it's '/abc' at that moment) doesn't match $location.url() and sends us back to '/abc' - so we are returning to point 1).
@timkindberg
Copy link
Contributor

Would you be able to create a plunkr?

@albv
Copy link
Author

albv commented Feb 20, 2014

Yes, sure http://plnkr.co/edit/UkzlV6vTexZ36XwP0Lnm?p=preview. But I don't know the way to tell Plunker to go to some non-existent url so this plunk doesn't demonstrate problem, all works as expected.

@timkindberg
Copy link
Contributor

Wouldn't this plunkr be your proper test case? http://plnkr.co/edit/qJioRE9UGTNX8WV4KneM?p=preview

Seems to be working fine to me.

@albv
Copy link
Author

albv commented Feb 20, 2014

No, as I mentioned before, if we successfully entered some state all subsequent redirects working fine. But if you run this example locally and point browser directly to localhost/#/abc it would be endlessly cycling.

@boroth
Copy link

boroth commented Mar 19, 2014

Any update on this? I'm having the same issue.

@boroth
Copy link

boroth commented Mar 19, 2014

I ended up just setting window.location.pathname rather than using $location.path() or $state.go().

@mghz
Copy link

mghz commented Apr 30, 2014

I'm using v1.2.16, is there a resolution for this issue?

I get an infinite loop when I try to implement authentication in the run event. I tried using state, location and windows.location all for the same result.

@mattcasey
Copy link

Your resolve functions should be returning a promise (or an object you want to inject). See docs: https://github.com/angular-ui/ui-router/wiki#resolve

So, instead of returning $q.reject() or $timeout(), you need to do:

var deferred = $q.defer();
AsyncFn(function (response) {
  if (CONDITION) {
    $q.resolve();
  } else {
    $q.reject();
  }
});
return $q.promise;

and

var deferred = $q.defer();
$timeout(function(){
  deferred.resolve();
, 1000);
return $q.promise;

@Gwash3189
Copy link

I believe what what @mattcasey meant is that you should return deferred.promise and not $q.promise.

Also, returning $q.reject() is ok as it returns a new resolved promise object.

Here are the docs for the $q service
https://docs.angularjs.org/api/ng/service/$q

@albv
Copy link
Author

albv commented May 15, 2014

@mattcasey the return value of registering a timeout function is a promise, which will be resolved when the timeout is reached and the timeout function is executed.

@evandrewry
Copy link

You can fix this by calling event.preventDefault() in the handler for $stateChangeError

$rootScope.$on('$stateChangeError', function (e, toState, toParams, fromState, fromParams, error) {
    if (error && error.redirect) {
        e.preventDefault()
        return $state.go(error.redirect);
    }

    $exceptionHandler(error);
});

@parliament718
Copy link

@evandrewry event.preventDefault() worked perfect thanks.

@jsonMartin
Copy link

@evandrewry : event.preventDefault() also worked for me...thank you so much for posting this! I was banging my head for hours until I came across this post.

@eddiemonge
Copy link
Contributor

Seems @evandrewry has the fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants