0

I've recently added a login feature to my AngularJS app.

I have a modal login, a login service, and its associated login controller. The modal works fine and does indeed return the user/password, which I can capture in my app.js in the .then() section here - loginService.loginModal().then. So far, so good.

However, I'm trying to SUSPEND state in my app.js. Currently I'm having issues with this, as my application continues to render the main page in the background as soon as my login modal appears. This also means that I can't display the proper userId on the main page.

I've tried to use event.preventDefault();, then $state.go once the user is authenticated; however, the prevent is causing an infinite loop in Angular.

(function () {
'use strict';
angular.module('rg', [
'ui.router',
'ui.bootstrap',
'ui.dashboard',
'kendo.directives'       
]).run(['$rootScope', '$state', 'userService', 'loginService', init]);

function init($rootScope, $state, userService, loginService) {
$rootScope.rageSessionVars = {};
$rootScope.$state = $state;

$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {            
    
    var requireLogin = toState.data.requireLogin;

    if (requireLogin && typeof $rootScope.userID == "undefined") {
    
 //event.preventDefault(); // CAUSING AN INFINITE LOOP ERR IN ANGULAR !!!
    // THE APP NEEDS TO BE SUSPENDED HERE UNTIL USER IS AUTHENTICATED !!
      
 loginService.loginModal().then(function (user) {
            
     $rootScope.userID = user.userId;
     initRz();
     return $state.go(toState.name, toParams);
 })                

    }
});        

function initRz() {                           
 userService.initRazor(razorEnvJson).then(function (data) {
     var response = data.status;
     if (response.match(/SUCCESS/g)) {
  userService.openUserSession(razorEnvJson).then(function (data) {
      var sessionID = data.data[0];
      $rootScope.rageSessionVars.sessionID = sessionID;
      $rootScope.rageSessionVars.userID = $rootScope.userID;                                
  });
     }
 });             
}
}
})();

And here is my routing config. It's note-worthy to say that I ONLY use the 'main' route, as my left nav menu contains a Kendo treeview in place of a standard routing menu:

angular
    .module('rage')
    .config(config);

function config($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("/dashboard");
    $stateProvider
        
        .state('main', {
            url: "/dashboard",
            templateUrl: "app/views/dashboard.html",
            controller: 'MainCtrl',
            data: { pageTitle: 'RG', requireLogin: true }            
        })
        .state('login', {
            url: "/login",
            templateUrl: "app/views/login-view.html",
            controller: 'LoginCtrl'
}

**** UPDATED $stateChangeStart EVENT CODE ****

I get past the if (requireLogin section, but once it hits $state.go it throws an error: TypeError: Cannot read property 'requireLogin' of undefined. So, I failed to re-route to the login view.

$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
    
    var requireLogin = toState.data.requireLogin;

    if (requireLogin && typeof $rootScope.userID == "undefined") {             
 $state.go('login');    // ERROR WHEN ATTEMPTING TO RE-ROUTE !!!
    }
});

* UPDATE, SEPT 18, 2015, USING RESOLVE IN UI-ROUTER *

Using resolve: in ui-router worked out for the most part; except I'm still seeing some page-rendering in the background. I was hoping to suspend all view-rendering UNTIL my loginService was resolved.

function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/dashboard");
$stateProvider
.state('main', {
    url: "/dashboard",
    templateUrl: "app/views/dashboard.html",
    controller: 'MainCtrl',
    data: { pageTitle: 'RAGE', requireLogin: true },
    resolve: {
 authUser: ['$rootScope', 'loginService', 'userService', function ($rootScope, loginService, userService) {
     return loginService.loginModal().then(function (user) {
  $rootScope.userID = user.userId;
  initSession(user, $rootScope, loginService, userService);
        return user;
     })
 }]
    }
})
.state('login', {
    url: "/login",
    templateUrl: "app/views/login-view.html",
    controller: 'LoginCtrl'
})
}
function initSession(user, $rootScope, loginService, userService) {

    userService.getInitParams().then(function (envJson) {
        $rootScope.rageSessionVars = razorEnvJson;
        userService.initRz(envJson).then(function (data) {
            var response = data.status;
            if (response.match(/SUCCESS/g)) {
                userService.openUserSession(envJson).then(function (data) {
                    var sessionID = data.data[0];
                    $rootScope.rageSessionVars.sessionID = sessionID;
                    $rootScope.rageSessionVars.userID = $rootScope.userID; // *** HOW TO SUSPEND ALL CODE UNTIL userID IS ASSIGNED ??? ***
                    console.log("sessionID = " + sessionID);

                    $rootScope.rageSessionVars.currDashboardName = "Default";
                });
            }
        });

I'd like to know if I'm missing something in my logic. Perhaps there's a Promise that I need to implement in the routing somewhere.

Your advice is appreciated.

regards,

Bob

bob.mazzo
  • 5,183
  • 23
  • 80
  • 149
  • 1
    define a separate service, contoller+state. If user is not logged-in in '$stateChangeStart' event, than do $state.go to that state. move modal code to that controller. – Praveen Prasad Sep 15 '15 at 18:44
  • I don't fully understand. I already have a separate `loginService`, then in the `.then()` section I do a `$state.go(toState.name, toParams)`. And by the way, I've updated my post to show the routing config. – bob.mazzo Sep 15 '15 at 19:07
  • 1
    --create a new state (may be login), its control should have your modal code (loginService.loginModal()....) -- Inside $stateChangeStart event, if(loginFails){$state.go(new_state_with_login_modal)}else {do_nothing} – Praveen Prasad Sep 15 '15 at 20:00
  • Okay, I think I'm following now. I can still keep the `$stateChangeStart` event inside my app.js, but if login fails then I can `$state.go` to the login route. And subsequently, inside my new `login` route I can reroute to my `main` route when user is authenticated ? – bob.mazzo Sep 15 '15 at 20:22
  • event.defaultPrevented = true; this should also work – Praveen Prasad Sep 16 '15 at 13:13
  • I'm failing on `$state.go('login')`. Please see my original post where I've added some code at the end of my post. See `*** UPDATED $stateChangeStart EVENT CODE ` – bob.mazzo Sep 16 '15 at 20:53
  • before `$state.go('login')` add `event.defaultPrevented = true; ` – Praveen Prasad Sep 17 '15 at 06:39

2 Answers2

1

Why not use ui-routers resolve. You don't need any $stateChangeStart and it will "hang" until the promise is resolved

elmalto
  • 1,008
  • 1
  • 14
  • 23
  • I was looking into `resolve` yesterday in fact, but unclear how to implement. And even using the event.preventDefault() is causing it to hand. – bob.mazzo Sep 16 '15 at 21:17
  • Using `resolve:` in `ui-router` worked out for the most part; however I had to post another question for details on how to implement the loginService. – bob.mazzo Sep 18 '15 at 13:42
0

instead of using the $stateChangeStart event, use $locationChangeStart. event.preventDefault() should work

Ronnie
  • 11,138
  • 21
  • 78
  • 140
  • preventDefault() just hangs in Angular with Infinite Loop error. Apparently it hangs until the login promise is resolved. There's another poster with the same scenario from last year, and yet he uses the same preventDefault(). Refer to: http://stackoverflow.com/questions/27212182/angularjs-ui-router-how-to-redirect-to-login-page – bob.mazzo Sep 21 '15 at 15:34