I am trying to implement a basic security check to block users from accessing certain states depending on their permission set:
'use strict';
var autoExecApp = angular.module("myApp", ['ui.router']);
autoExecApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){
$stateProvider
.state('index', {
url: '/index',
templateUrl: 'partials/index.html'
})
.state('permission', {
url: '/permission',
templateUrl: 'partials/permissions.html'
});
}]);
autoExecApp.run(['$rootScope', '$state', '$userRoles', function($rootScope, $state, $userRoles) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
event.preventDefault(); // prevent page from being loaded anyway
$userRoles.hasPermissions(toState.name).then(function(data) {
var result = data === "true";
if (!result) {
$state.go('permission');
}
else {
$state.go(toState.name, toParams ,{notify:false});
}
});
});
}]);
Now the problem surrounds the fact that my state change happens inside of a promise. I would happily make it synchronous - as it really should be in this case but I am confused as to why it is not working asynchronously.
If I move the event.preventDefault()
inside the promise, it works. BUT does the state change in 2 steps. First, it will always go to the original page and then it will soon after transition to the 'blocked' page (if it was blocked). I gather this is because it returns successfully once from the $on and updates the state, and then updates it again when it returns from the promise.
If the promise is how you see it however, the state will not change. Everything appears to be working, but the page will not update the view. I am not sure why this is the case. For some reason, if the event is not prevented, the later state change will work. However, if it is prevented and the $state.go is called from the returned promise, it appears to be too late to update the $state.
EDIT: I had success using the broadcast solution and this was what it looked like:
event.preventDefault();
$state.go(toState.name, toParams, {notify: false}).then(function() {
// line 907 state.js
$rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
});
Apparently due to it being in a promise, the preventDefault(); suppresses this '$stateChangeSuccess' from naturally firing. Performing it manually restores the expected behaviour.