8

I'm using ui.router for routing in my Angular app. There is a typical login scenario, where I'm redirecting a user to the login url if he's not logged in. Without the ui.router library, this worked fine using $routeChangeStart event combined with $location.path. Now, I'm using the $stateChangeStart event, combined with $state.go, and the nothing works! It also sends my browser into an infinite loop. I read from other sources that this is a known bug, and none of the suggested solutions work for me. Moreover, $location.path too doesn't redirect to the login page.

This is how I've configured my paths:

 .config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
    $stateProvider
        .state('main', {
            url: '/',
            templateUrl: 'views/main.html',
            controller: 'MainCtrl'
        })
        .state('about', {
            url: '/about',
            templateUrl: 'views/about.html',
            controller: 'AboutCtrl'
        })
        .state('login', {
            url: '/login',
            templateUrl: 'views/loginform.html',
            controller: 'LoginCtrl'
        });
    $urlRouterProvider.otherwise("/");
}])

And my run method:

.run(['$state', '$rootScope', '$location', function($state, $rootScope, $location) {
    //Check when routing starts
    //event, next, current
    $rootScope.$on( '$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
        //Redirect to login if the user is not logged in
        if (!isUserLoggedIn) {

            //Some suggestion
            //e.preventDefault();
            console.log('Not logged in');

            //Infinite loop, kills my browser!
            $state.go('login');
            $state.transitionTo('login');

            //Some suggestion
            $state.go('login', { url: '/login'});

            //Doesn't work
            $location.path('/login');

            //$location.path( $state.href('login');
            console.log('Redirected');
        }
    });
laser
  • 1,388
  • 13
  • 14
Rutwick Gangurde
  • 4,772
  • 11
  • 53
  • 87

2 Answers2

9

What we would need here, is to do NOT redirect, if the state TO is already the 'login'.

Just a drafted adjustment would be

$rootScope.$on( '$stateChangeStart', function(e, toState  , toParams
                                               , fromState, fromParams) {

    var isLogin = toState.name === "login";

    if(isLogin){

       return; // no need to redirect anymore 
    }
    ...

And also, we should call $state.go('login'); with the event.preventDefault();

 ...
 // also prevent default on redirect
 $state.go('login');
 event.preventDefault(); 
 ...

Please check this Q & A How can I fix 'Maximum call stack size exceeded' AngularJS And a plunker with similiar solution

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • But my logged in flag is actually a function that returns a boolean and it is a part of some service. How do I use it in this situation? Also, won't the infinite loop show up everywhere? – Rutwick Gangurde Oct 01 '14 at 06:40
  • Could you check this answer: http://stackoverflow.com/a/24725180/1679310? and try this plunker http://plnkr.co/edit/JLABnz?p=preview? It should show how it could work. There is also some Extern service `User` returning boolean, but the logic if/where to redirect is still evaluted in the $stateChangeStart. Does this help? – Radim Köhler Oct 01 '14 at 07:05
  • Great! `preventDefault` works in the event handler, and now the redirection to login page works fine. But, in my logout function, I use $state.go for redirecting to the login page, it still leads to a loop. Logout is triggered by a button click hence I can use preventDefault. – Rutwick Gangurde Oct 01 '14 at 07:44
  • 1
    Please do not take me wrong. Please, if I can ask you, slow down, take a breath ;) It should not be so difficult. Just think about your application. If there is logout... redirecting to "main" or "home" just add these to the condition at the begining like (if Login or if Main Or if Home...) return... do not redirect. It is just a bout more precise settings. Does this help? I am sure you will make it in a minute. You are almost there;) – Radim Köhler Oct 01 '14 at 07:58
  • Thanks, you actually made me think! :) Now it's almost done. One more thing, how do I stay on the current page, if the user is logged in? – Rutwick Gangurde Oct 01 '14 at 09:13
  • I would say, (maybe I see it too simple) that this should be really easy. We can have a check: IsAuthenticated? yes? then return... nor redirect... DO I answer the question? Or... – Radim Köhler Oct 01 '14 at 09:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62239/discussion-between-rutwick-gangurde-and-radim-kohler). – Rutwick Gangurde Oct 01 '14 at 09:16
  • Yes, you did answer it! Just one thing pending and I've moved it to the discussion! – Rutwick Gangurde Oct 01 '14 at 09:21
1

I used something similar to this:

MyApp.run(['$rootScope', '$state', function ($rootScope, $state) {
  // The event
  $rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
    // Is user already logged in?
    var isUserLoggedIn = $rootScope.isUserLoggedIn();

    if (isUserLoggedIn) {
      // don't let user visit login state, he's already logged in.
      $state.go('home');
      // preventDefault as described in @radim's answer
      e.preventDefault();
    } else if (toState.name === "login") {
      // user is not logged in and is not going to login state right now, send him there first.
      $state.go('login');
      // preventDefault as described in @radim's answer
      e.preventDefault();
    }
  });
}]);
GabLeRoux
  • 16,715
  • 16
  • 63
  • 81