Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Defer a StateChangeStart event until after a ui-router resolve executes

I have an angular app that uses authorization logic and ui-router to bar unauthorized users for certain states/views. I follow the standard approach of listening for a stateChange event, which triggers my authorization logic. This all works well until the dreaded page re-load.

I store session data (including authorization status) in local storage so that on page reloads I can use a parent state in ui-router to first resolve/get the authorization status from local storage prior to attempting to change views. Here is the configuration of my app parent state object:

$stateProvider.
state('app', {
  url: '/app',
  abstract: true,
  controller: 'appCtrl',
  data: {
    authorizedRoles: [USER_ROLES.all]
  },
  templateUrl: 'partials/app.html',
  resolve: {

    //Try to restore from the previous session before loading any of the app child states
    RestoredSession: ['SessionService',
             function(SessionService){
                return SessionService.restoreSession();
              }]
  }
})

...various app. child states

And here is my onStateChange listener:

//listen for a ui.router $stateChangeStart event and test the new path to see if the currentUser
//is authorized to view that page

.run(         ['$rootScope', 'AUTH_EVENTS', 'SessionService', 
  function ($rootScope,   AUTH_EVENTS,   SessionService) {

  $rootScope.$on('$stateChangeStart', function (event, next) {
    var authorizedRoles = next.data.authorizedRoles;
    //If the requested page allows guest access, then continue to stateChange
    if (authorizedRoles.indexOf('guest') !== -1 || authorizedRoles.indexOf('*') !== -1) return;

    //If the requested page requires authorization, check login and auth privileges
    if (!SessionService.isAuthorized(authorizedRoles)) {

      event.preventDefault();

      if (SessionService.existingSession()) {

        // user is not allowed
        $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
        console.log("User attempted to access page for which he is not authorized");

      } else {

        // user is not logged in
        $rootScope.$broadcast(AUTH_EVENTS.notLoggedIn);
        console.log("User attempted to access page when he is not logged in");

      }
    }
  });

}]);

My problem is that the stateChangeStart event is triggering prior to the app resolve such that the listener stops the state change (via the event.preventDefault), and then my resolve loads the stored session data, which often establishes that the user was authorized all along. If I could require execution of the resolve prior to the event triggering then I'd be golden.

Any ideas out there???

BTW, here is a similar SO question that went unanswered: Defer Angular UI Router $stateChangeStart until server authorization response receieved

like image 974
chuck w Avatar asked Sep 12 '14 02:09

chuck w


1 Answers

Turns out that all I needed to do was move the loading of config data to the .run() block instead of trying to do it in the parent app state's resolve.

//listen for a ui.router $stateChangeStart event and test the new path to see if the currentUser
//is authorized to view that page
.run(         ['$rootScope', 'AUTH_EVENTS','SessionService', 'localStorageService',
  function ($rootScope,   AUTH_EVENTS,  SessionService,   localStorageService) 
  {
  $rootScope.$on('$stateChangeStart', function (event, next) {

    //function to check to see if the currentUser has one of the required roles to authorize the next state.
    var checkAuthorization = function(authorizedRoles){

         //If the requested page allows guest access, then continue to stateChange
         if (authorizedRoles.indexOf('guest') !== -1 || authorizedRoles.indexOf('*') !== -1) return;
         //If the requested page requires authorization, check login and auth privileges
         if (!SessionService.isAuthorized(authorizedRoles)) {
           event.preventDefault();
           if (SessionService.existingSession()) {
             // user is not allowed
             $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
             console.log("User attempted to access page for which he is not authorized");
           } else {
             // user is not logged in
             $rootScope.$broadcast(AUTH_EVENTS.notLoggedIn);
             console.log("User attempted to access page when he is not logged in");
           }
         }
       };

    //Before calling checkAuthorization(), test to see if the state change was triggered by a reload
    //If so, load config data before triggering the `checkAuthorization()` function.
    if (SessionService.freshLoad === true  || typeof SessionService.freshLoad === 'undefined'){
      SessionService.freshLoad = false;
      var storedUser = localStorageService.get('currentUser');

      //If we have a stored user but no existing session, then we know that we have stored
      //user data to reload before the checkAuthorization() function.
      if (typeof storedUser !== "undefined" && storedUser !== null && !SessionService.existingSession()) {
        SessionService.restoreSession();
      }
    }

  checkAuthorization(next.data.authorizedRoles);

  });

}]);
like image 141
chuck w Avatar answered Oct 04 '22 15:10

chuck w