I'm trying to implement a user auth check and access check system in my app, however I keep hitting roadblocks. I think I have it the correct way this time but I have one last hurdle.
A little background: I tried putting all of the code into the $rootScope.on($startChangeStart) and it worked, horribly... but it worked. The route was always redirected but due to the auth check on the backend it displayed the first request page for 1/2 a second and then the redirect page every time. Thus I tried 'pausing' page load by calling evt.preventDefault() right at the start of the $startChangeStart function, which worked, but trying to put the user back to the original route afterwards caused an infinite loop in the router.
So after more research and reading a lot of stack posts I'm certain that 'resolve:' is the proper place to put the auth check to ensure the page is not loading while it occurs, and then redirect the user if needed from the $startChangeStart. ($state and event are always undefined in my attempts to inject them into a resolve function) It seems like the winning combination.
My problem: I have the resolve on the root state in my app: 'main'
This was to avoid code redundancy, however I cannot determine how to access the root state's properties, and therefore the resolve result, from the $stateChangeStart function. The toState is the child state, while the fromState is either the previous state or an abstract state with the '^' route...
Do I have to put the resolve on every child state for this to work, or is there a way to access the root state from this point?
Basic app setup:
angular.module('App', ['ui.router', 'ui.bootstrap', 'ui.event', 'AngularGM', 'ngResource'])
.config(['$urlRouterProvider', '$stateProvider', function($urlRouterProvider, $stateProvider){
$urlRouterProvider
.when('/home', '/')
.when('', '/')
.when('/sign-up/joe', '/sign-up')
.otherwise('/');
$stateProvider
.state('main', {
url: '',
abstract: true,
templateUrl: 'views/main.html',
controller: 'MainCtrl',
resolve: {
checkAccess: ['accountService', function(accountService) {
accountService.checkAuth(function(){
accountService.checkAccess(function (access){
return access;
});
});
}]
}
})
.state('main.home', {
url: '',
abstract: true,
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
})
.state('main.home.index', {
url: '/',
templateUrl: 'views/home/index.html'
});
.run(['$rootScope', '$state', '$stateParams', 'accountService', function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
console.dir(toState);
console.dir(toParams);
console.dir(fromState);
console.dir(fromParams);
if (!toState.checkAccess.allowed) {
event.preventDefault();
$state.transitionTo(toState.checkAccess.newState);
}
});
}]);
This is the output from the console.dir() calls on the two state objects:
Object
name: "main.home.index"
templateUrl: "views/home/index.html"
url: "/"
__proto__: Object
Object
controller: "PlacesCtrl"
name: "main.places.search"
templateUrl: "views/places.html"
url: "/places"
__proto__: Object
Update
Oops, forgot to mention AngularJS version is v1.2.0-rc.2
$state.current console.dir()
Object
abstract: true
name: ""
url: "^"
views: null
__proto__: Object
If you initialize your state with a default, empty object on resolve, you'll be able to manipulate it within
$stateChangeStart
.See https://github.com/angular-ui/ui-router/issues/1165
You need not to use
resolve
. Take a look at my solution:I use angular_devise and ngDialog but they are optional and you can implement it with your own user's service.
Yes, I believe you can access root state from the
$stateChangeStart
function.When using pure AngularJS I normally use
current.$$route
For example, using the following route
I can access the root state like so
Using
ui-router
it's just a bit different as it's called$state.current
. And you can access all the properties associated to whatever route you hit (e.g: $state.current.url)So on your code you could have something like this
Is it possible to do the redirect from within your
accountService
? If you detect that the user fails your checkAuth or checkAccess functions, you could prevent the callback from executing and redirect the user to your error (or login) page.Something else to consider is implementing some sort of variable/queue of states if you'd like to redirect someone to the login page to refresh their authorization/authentication and then return to the previous state.
This answer is very late but it can be useful.
Resolves of a state and
$stateChangeStart
event are executed at the same time. By the time you try to access resolved data in$stateChangeStart
, it'll not be available but it'll be available when$stateChangeSuccess
event fires.If you use
$stateChangeStart
then you'll need to docheckAuth
from two places$stateChangeStart
event and main resolve. Since they have parallel execution, at least 2 network requests will be sent to server for the same data.Instead use
$stateChangeSuccess
. Using this will ensure that your resolves are resolved and you can then check access. Also, instead of accessing resolved properties,access resolved data using angular service.