javascriptangularjsangular-ui-routerstates

Verify login on state change in AngularJS doesn't work well


I want to verify if the user can access a state before he gets there, if he doesn't have permissions will be redirected to another page. The problem is that I'm doing a SPA and it verifies the permissions, but it takes a while until the server send the response and the user is redirected, so what happen is that a screen appears for 1 or 2 seconds and then is redirected successfully. Is there anyway to avoid this?

This is the code for the state change:

webApp.run(function ($rootScope, $state, StateService) {

    $rootScope.$on('$stateChangeStart', function (event, toState, fromState, toParams) {

        StateService.hasAccessTo(toState.name, function(data){
            if (data.data != ""){
                event.preventDefault();
                $state.go(data.data);
            }
        });

    });
});

and the service:

webApp.service('StateService', function($http, $rootScope){
    this.hasAccessTo = function(state, callback){
        $http.get("state/" + state).then(callback);
    }
});

I have also tried with a promise in the $stateChangeStart, but it didn't work.

I read about interceptors, but they work if the user is in another page and access mine, if he is already on the page and type a link manually it doesn't intercepts.

Any modifications or suggestions of new ideas or improvements are welcome!

EDIT

Now I have this:

var hasAccessVerification = ['$q', 'StateService', function ($q, $state, StateService) {
    var deferred = $q.defer();

    StateService.hasAccessTo(this.name, function (data) {
        if (data.data !== '') {
            $state.go(data.data);
            deferred.reject();
        } else {
            deferred.resolve();
        }
    });

    return deferred.promise;
}];

$urlRouterProvider.otherwise("/");
$compileProvider.debugInfoEnabled(false);
$stateProvider
    .state('welcome',{
        url:"/",
        views: {
            'form-view': {
                templateUrl: '/partials/form.html',
                controller: 'Controller as ctrl'
            },
            '@': {
                templateUrl: '/partials/welcome.html'
            }
        },
        data: {
            requireLogin: false
        },
        resolve: {
            hasAccess: hasAccessVerification
        }
    })

And it validates, but it doesn't load the template. It doesn't show de views. What might I be doing wrong?

EDIT 2

I forgot to add $state here:

var hasAccessVerification = ['$q', '$state', 'StateService', function ($q, $state, StateService){...}

Solution

  • Consider using the resolve in your state configuration instead of using $stateChangeStart event.

    According to the docs:

    If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.

    Example:

    var hasAccessFooFunction = ['$q', 'StateService', function ($q, StateService) {
        var deferred = $q.defer();
    
        StateService.hasAccessTo(this.name, function (data) {
            if (data.data !== '') {
                $state.go(data.data);
                deferred.reject();
            } else {
                deferred.resolve();
            }
        });
    
        return deferred.promise;
    }];
    
    $stateProvider
        .state('dashboard', {
            url: '/dashboard',
            templateUrl: 'views/dashboard.html',
            resolve: {
                hasAccessFoo: hasAccessFooFunction
            }
        })
    
        .state('user', {
            abstract: true,
            url: '/user',
            resolve: {
                hasAccessFoo: hasAccessFooFunction
            },
            template: '<ui-view/>'
        })
        .state('user.create', {
            url: '/create',
            templateUrl: 'views/user/create.html'
        })
        .state('user.list', {
            url: '/list',
            templateUrl: 'views/user/list.html'
        })
        .state('user.edit', {
            url: '/:id',
            templateUrl: 'views/user/edit.html'
        })
        .state('visitors', {
            url: '/gram-panchayat',
            resolve: {
                hasAccessFoo: hasAccessFooFunction
            },
            templateUrl: 'views/visitor/list.html'
        });
    

    And according to the docs https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies resolve are inherited:

    New in version 0.2.0

    Child states will inherit resolved dependencies from parent state(s), which they can overwrite. You can then inject resolved dependencies into the controllers and resolve functions of child states.

    But, please note:

    The resolve keyword MUST be on the state not the views (in case you use multiple views).