3

Edit: The proposed approach of using resolve isn't working for me. The $stateChangeStart event seems to be happening before the resolve stuff. Plunker.

I'm using $on($stateChangeStart) to do authentication for routes. The way I was doing it, there was a problem: if an user was trying to access a state he's unauthorized to see, the state would be shown briefly before my code redirected him.

To get around this, the best way seems to be to use event.preventDefault() + $state.go(toState.name, {}, { notify: false }), but { notify: false } has a known bug.

I'm not sure what a good way of handling this situation is. The only idea I've got is to toggle the display property of body, but I don't like that solution.

  1. The "flickering" display: none -> display: block on body.
  2. It's expensive to hide and redisplay such a large portion of the DOM (or is it?).

How should this situation be handled?

Plunker

angular
  .module('app', ['ui.router'])
  .config(config)
  .run(run)
;

function config($locationProvider, $urlRouterProvider, $stateProvider) {
  $locationProvider.html5Mode(true);
  $urlRouterProvider.otherwise('/');
  
  $stateProvider
    .state('home', {
      url: '/',
      template: '<p>home</p>'
    })
    .state('one', {
      url: '/one',
      template: '<p>one</p>',
      authenticate: true
    })
    .state('two', {
      url: '/two',
      template: '<p>two</p>'
    })
  ;
}

function run($rootScope, $state, $timeout) {
  $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
    if (toState.authenticate) {
      // event.preventDefault();
      angular.element(document.body).css('display', 'none');
      if (isAuthenticated(false)) {
        // $state.go(toState.name, {}, { notify: false });
        angular.element(document.body).css('display', 'block');
      }
      else {
        $timeout(function() {
          $state.go('two');
          angular.element(document.body).css('display', 'block');
        }, 2000);
      }
    }
  });
}

function isAuthenticated(hardcode) {
  return hardcode; 
}
<!DOCTYPE html>
<html ng-app='app'>

  <head>
    <script data-require="angular.js@1.4.6" data-semver="1.4.6" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.min.js"></script>
    <script data-require="ui-router@0.2.11" data-semver="0.2.11" src="https://rawgit.com/angular-ui/ui-router/0.2.11/release/angular-ui-router.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
    <base href='/'>
  </head>

  <body>
    <ul>
      <li><a ui-sref='home'>home</a></li>
      <li><a ui-sref='one'>state one</a></li>
      <li><a ui-sref='two'>state two</a></li>
    </ul>
    
    <div ui-view></div>
  </body>

</html>
Adam Zerner
  • 17,797
  • 15
  • 90
  • 156

1 Answers1

2

Your if was missing !.

Preventing the event takes care of the problem and doesn't allow the route to load

function run($rootScope, $state, $timeout) {
  $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
    if (toState.authenticate) {
      if (!isAuthenticated()) {
        event.preventDefault();
        // can redirect if needed here
      }
    }
  });
}

function isAuthenticated() {
  return false; 
}

DEMO

charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • I intended for it to be the way it is. When `isAuthenticated()` returns `true`, the route change should go through. I know that `event.preventDefault()` will cause the route change to not go through. The problem is that I want to call `event.preventDefault()`, and then "reverse it" if the user is authorized. The reason for that is because in my real app, I have to wait for some asynchronous task before knowing whether they're authorized, and I don't want the page to show while they're waiting, so I want to call `event.preventDefault()` before the task and reverse it afterwards (if authorized). – Adam Zerner Oct 10 '15 at 23:19
  • Can use a resolve in a parent state for all authentication routes to get User data and set authorization. By having all routes needing authorization under one parent if parent state resolve gets rejected, none of the child states can be accessed – charlietfl Oct 10 '15 at 23:31