验证 AngularJS 状态更改时的登录效果不佳

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

我想在用户到达之前验证用户是否可以访问状态,如果他没有权限将被重定向到另一个页面。 问题是我正在做一个 SPA 并且它验证权限,但是在服务器发送响应并且用户被重定向之前需要一段时间,所以发生的事情是屏幕出现 1 或 2 秒然后被重定向成功地。有没有办法避免这种情况?

这是状态更改的代码:

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);
            }
        });

    });
});

和服务:

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

我也试过 $stateChangeStart 中的 promise,但没用。

我读到了拦截器,但如果用户在另一个页面并访问我的拦截器,它们就会起作用,如果他已经在页面上并手动输入 link 它不会拦截。

欢迎任何新想法或改进的修改或建议!

编辑

现在我有了这个:

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
        }
    })

它会验证,但不会加载模板。它不显示 de views。我可能做错了什么?

编辑 2

我忘了在这里添加 $state:

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

考虑在您的状态配置中使用 resolve 而不是使用 $stateChangeStart 事件。

根据文档:

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.

示例:

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'
    });

并且根据文档 https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies resolve 是继承的:

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.

但是,请注意:

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

最佳做法是在 responseError 上设置 interceptor,它会检查响应状态并采取相应措施:

webApp.config(['$httpProvider' ($httpProvider) {
    var interceptor = ['$q', '$rootScope', function ($q, $rootScope) {
                return {
                    request: function (config) {
                        // can also do something here
                        // for example, add token header
                        return config;
                    },

                    'responseError': function (rejection) {
                        if (rejection.status == 401 && rejection.config.url !== '/url/to/login') {
                             // If we're not on the login page
                             $rootScope.$broadcast('auth:loginRequired');
                          }
                        }
                        return $q.reject(rejection);
                    }

                }
            }];
            $httpProvider.interceptors.push(interceptor);
}]);

并在 run 块中处理重定向

webApp.run(['$rootScope', function($rootScope){
     $rootScope.$on('auth:loginRequired', function () {
                $state.go('loginState');
            });
}]);

好处是$state服务不需要处理权限逻辑:

$stateProvider
        .state('someState', {
            url: '/some-state',
            templateUrl: '/some-state.html',
            resolve: {
                dataFromBackend: ['dataService', function (postingService) {
                    // if the request fails, the user gets redirected
                    return dataService.getData();
                }],
            },
            controller: function ($scope, dataFromBackend) {

            }
        })

通知

使用这种方法,您不需要 StateService,您需要做的就是 return 来自后端的正确响应状态。例如,如果用户是访客,则 return 401 status.