2

I am using AngularJS ui-router. I am trying to implement protecting routes for unauthenticated user. I am checking if user is logged in on $stateChangeStart. If the user is not logged in then redirect to login state.

But when i am using $state.go("login") in stateChangeStart handler, the handler code goes in infinite loop and getting console error "RangeError: Maximum call stack size exceeded"

Below is my code:

$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams) {
    var allowedStates = ["signup","confirmaccount","resetPassword"];
    if(!$window.localStorage.getItem('userInfo') && !(allowedStates.includes($state.current.name)))
    {
        $state.go("login");
    }
}
);

And below is the screenshot of console error.

enter image description here

Ansuya Sindha
  • 119
  • 1
  • 8

3 Answers3

2

Prevent the default behavior and check for allowed state without using $state.current.name since toState is already a parameter to $stateChangeStart

Update

I think you need here a No State Change logic rather than redirecting to login always.

$rootScope.$on('$stateChangeStart',
  function(event, toState, toParams, fromState, fromParams) {
    var noChangeStates = ["login", "signup", "confirmaccount", "resetPassword"];
    var noStateChange = noChangeStates.indexOf(toState.name) > -1;

    if (noStateChange) {
      return;
    }

    //Check for Allowed or Not Allowed logic here then redirect to Login
    if (!$window.localStorage.getItem('userInfo')) {
        event.preventDefault();
        $state.go("login")
    }
  }
);

Please note, you should also add "login" to No state change

Sajal
  • 4,359
  • 1
  • 19
  • 39
0

But when i am using $state.go("login") in stateChangeStart handler, the handler code goes in infinite loop and getting console error "RangeError: Maximum call stack size exceeded"

Looks like you always call $state.go("login");

You can check toState and fromState to avoid calling additional time $state.go("login");

Something like:

if(!$window.localStorage.getItem('userInfo') 
   && !(allowedStates.includes($state.current.name))
   && fromState.name !== 'login'
){
    event.preventDefault();
    $state.go("login");
 }
Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
0

Using a stateChange event is not the best way to handle that. Actually, what it does:

  • You change state
  • Then you check for the authentication.

It would be better to check before changing the state. For this, you can use ui-router's resolve:

$stateProvider
  .state('login', { // Login page: do not need an authentication
    url: '/login',
    templateUrl: 'login.html',
    controller: 'loginCtrl',
  })
  .state('home', { // Authenticated users only (see resolve)
    url: '/home',
    templateUrl: 'home.html',
    controller: 'homeCtrl',
    resolve: { authenticate: authenticate }
  });

function authenticate($q, user, $state, $timeout) {
  if (user.isAuthenticated()) {
    // Resolve the promise successfully
    return $q.when()
  } else {
    // The next bit of code is asynchronously tricky.

    $timeout(function() {
      // This code runs after the authentication promise has been rejected.
      // Go to the log-in page
      $state.go('logInPage')
    })

    // Reject the authentication promise to prevent the state from loading
    return $q.reject()
  }
}

See also this answer.

Mistalis
  • 17,793
  • 13
  • 73
  • 97
  • Thanks for your answer. I have tried using resolve before. But in that case i must resolve authenticate function for every state. And i don't need that. I want to check it from one place using state event handler. – Ansuya Sindha Aug 30 '17 at 13:03