根据情况redirect到一定的路线
我正在编写一个小的AngularJS应用程序,它有一个login视图和一个主视图,configuration如下:
$routeProvider .when('/main' , {templateUrl: 'partials/main.html', controller: MainController}) .when('/login', {templateUrl: 'partials/login.html', controller: LoginController}) .otherwise({redirectTo: '/login'});
我的LoginController检查用户/传递组合,并在$ rootScope上设置一个属性来反映这一点:
function LoginController($scope, $location, $rootScope) { $scope.attemptLogin = function() { if ( $scope.username == $scope.password ) { // test $rootScope.loggedUser = $scope.username; $location.path( "/main" ); } else { $scope.loginError = "Invalid user/pass."; } }
一切正常,但如果我访问http://localhost/#/main
我最终绕过login屏幕。 我想写一些东西,如“每当路由发生变化,如果$ rootScope.loggedUser为空,然后redirect到/login”
…
等等 我能以某种方式听取路线变化吗? 无论如何,我会发布这个问题,并继续寻找。
经过一些文件和源代码的潜水后,我觉得我的工作。 也许这对其他人有用?
我将以下内容添加到模块configuration中:
angular.module(...) .config( ['$routeProvider', function($routeProvider) {...}] ) .run( function($rootScope, $location) { // register listener to watch route changes $rootScope.$on( "$routeChangeStart", function(event, next, current) { if ( $rootScope.loggedUser == null ) { // no logged user, we should be going to #login if ( next.templateUrl == "partials/login.html" ) { // already going to #login, no redirect needed } else { // not going to #login, we should redirect now $location.path( "/login" ); } } }); })
有一件事看起来很奇怪,那就是我必须testing部分名称( login.html
),因为“next”路由对象没有url或其他东西。 也许有更好的办法?
这可能是一个更加优雅和灵活的解决scheme,它具有“parsing”configuration属性和“承诺”,可根据数据在路由和路由规则上加载最终的数据。
在路由configuration中的“parsing”中以及在函数加载和检查数据中指定一个函数,执行所有redirect。 如果你需要加载数据,你会返回一个承诺,如果你需要做redirect – 在此之前拒绝承诺。 所有的细节都可以在$ routerProvider和$ q文档页面find。
'use strict'; var app = angular.module('app', []) .config(['$routeProvider', function($routeProvider) { $routeProvider .when('/', { templateUrl: "login.html", controller: LoginController }) .when('/private', { templateUrl: "private.html", controller: PrivateController, resolve: { factory: checkRouting } }) .when('/private/anotherpage', { templateUrl:"another-private.html", controller: AnotherPriveController, resolve: { factory: checkRouting } }) .otherwise({ redirectTo: '/' }); }]); var checkRouting= function ($q, $rootScope, $location) { if ($rootScope.userProfile) { return true; } else { var deferred = $q.defer(); $http.post("/loadUserProfile", { userToken: "blah" }) .success(function (response) { $rootScope.userProfile = response.userProfile; deferred.resolve(true); }) .error(function () { deferred.reject(); $location.path("/"); }); return deferred.promise; } };
对于讲俄语的人们来说, AngularJS上有一个post“Вариантусловногораутингав”。
我一直在试图做同样的事情。 和同事一起工作后,又想出了另一个更简单的解决scheme。 我在$location.path()
上设置了一个手表。 这就是诀窍。 我刚开始学习AngularJS,发现这个更清洁和可读。
$scope.$watch(function() { return $location.path(); }, function(newValue, oldValue){ if ($scope.loggedIn == false && newValue != '/login'){ $location.path('/login'); } });
实现loginredirect的另一种方式是使用这里描述的事件和拦截器。 本文介绍了一些额外的优点,例如检测何时需要login,排队请求,以及在login成功后重播它们。
您可以在这里尝试一个工作演示,并在这里查看演示源代码。
1.设置全局当前用户。
在您的身份validation服务中,将当前已通过身份validation的用户设置为根作用域。
// AuthService.js // auth successful $rootScope.user = user
2.在每个受保护的路由上设置authfunction。
// AdminController.js .config(function ($routeProvider) { $routeProvider.when('/admin', { controller: 'AdminController', auth: function (user) { return user && user.isAdmin } }) })
3.检查每个路由更改的权威性。
// index.js .run(function ($rootScope, $location) { $rootScope.$on('$routeChangeStart', function (ev, next, curr) { if (next.$$route) { var user = $rootScope.user var auth = next.$$route.auth if (auth && !auth(user)) { $location.path('/') } } }) })
或者,您可以在用户对象上设置权限并为每个路由分配权限,然后检查事件callback中的权限。
以下是我的做法,以防万一:
在configuration中,我在publicAccess
设置了publicAccess
属性(如login或注册):
$routeProvider .when('/', { templateUrl: 'views/home.html', controller: 'HomeCtrl' }) .when('/login', { templateUrl: 'views/login.html', controller: 'LoginCtrl', publicAccess: true })
然后在运行块中,我设置了一个$routeChangeStart
事件中的侦听器,该事件redirect到'/login'
除非用户有权访问或路由可公开访问:
angular.module('myModule').run(function($rootScope, $location, user, $route) { var routesOpenToPublic = []; angular.forEach($route.routes, function(route, path) { // push route onto routesOpenToPublic if it has a truthy publicAccess value route.publicAccess && (routesOpenToPublic.push(path)); }); $rootScope.$on('$routeChangeStart', function(event, nextLoc, currentLoc) { var closedToPublic = (-1 === routesOpenToPublic.indexOf($location.path())); if(closedToPublic && !user.isLoggedIn()) { $location.path('/login'); } }); })
你显然可以改变从isLoggedIn
到其他任何东西的条件…只是显示另一种方式来做到这一点。
我正在使用拦截器。 我创build了一个可以添加到index.html文件的库文件。 这样你就可以对你的rest服务调用进行全局error handling,而不必单独关心所有的错误。 再往下,我也粘贴了我的basic-authlogin库。 在那里你可以看到,我也检查401错误,并redirect到一个不同的位置。 参见lib / ea-basic-auth-login.js
LIB / HTTP的错误handling.js
/** * @ngdoc overview * @name http-error-handling * @description * * Module that provides http error handling for apps. * * Usage: * Hook the file in to your index.html: <script src="lib/http-error-handling.js"></script> * Add <div class="messagesList" app-messages></div> to the index.html at the position you want to * display the error messages. */ (function() { 'use strict'; angular.module('http-error-handling', []) .config(function($provide, $httpProvider, $compileProvider) { var elementsList = $(); var showMessage = function(content, cl, time) { $('<div/>') .addClass(cl) .hide() .fadeIn('fast') .delay(time) .fadeOut('fast', function() { $(this).remove(); }) .appendTo(elementsList) .text(content); }; $httpProvider.responseInterceptors.push(function($timeout, $q) { return function(promise) { return promise.then(function(successResponse) { if (successResponse.config.method.toUpperCase() != 'GET') showMessage('Success', 'http-success-message', 5000); return successResponse; }, function(errorResponse) { switch (errorResponse.status) { case 400: showMessage(errorResponse.data.message, 'http-error-message', 6000); } } break; case 401: showMessage('Wrong email or password', 'http-error-message', 6000); break; case 403: showMessage('You don\'t have the right to do this', 'http-error-message', 6000); break; case 500: showMessage('Server internal error: ' + errorResponse.data.message, 'http-error-message', 6000); break; default: showMessage('Error ' + errorResponse.status + ': ' + errorResponse.data.message, 'http-error-message', 6000); } return $q.reject(errorResponse); }); }; }); $compileProvider.directive('httpErrorMessages', function() { return { link: function(scope, element, attrs) { elementsList.push($(element)); } }; }); }); })();
CSS / HTTP的错误handling.css
.http-error-message { background-color: #fbbcb1; border: 1px #e92d0c solid; font-size: 12px; font-family: arial; padding: 10px; width: 702px; margin-bottom: 1px; } .http-error-validation-message { background-color: #fbbcb1; border: 1px #e92d0c solid; font-size: 12px; font-family: arial; padding: 10px; width: 702px; margin-bottom: 1px; } http-success-message { background-color: #adfa9e; border: 1px #25ae09 solid; font-size: 12px; font-family: arial; padding: 10px; width: 702px; margin-bottom: 1px; }
的index.html
<!doctype html> <html lang="en" ng-app="cc"> <head> <meta charset="utf-8"> <title>yourapp</title> <link rel="stylesheet" href="css/http-error-handling.css"/> </head> <body> <!-- Display top tab menu --> <ul class="menu"> <li><a href="#/user">Users</a></li> <li><a href="#/vendor">Vendors</a></li> <li><logout-link/></li> </ul> <!-- Display errors --> <div class="http-error-messages" http-error-messages></div> <!-- Display partial pages --> <div ng-view></div> <!-- Include all the js files. In production use min.js should be used --> <script src="lib/angular114/angular.js"></script> <script src="lib/angular114/angular-resource.js"></script> <script src="lib/http-error-handling.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> <script src="js/filters.js"></script>
LIB / EA-基本authentication- login.js
几乎可以做同样的login。 在这里你可以得到redirect的答案($ location.path(“/ login”))。
/** * @ngdoc overview * @name ea-basic-auth-login * @description * * Module that provides http basic authentication for apps. * * Usage: * Hook the file in to your index.html: <script src="lib/ea-basic-auth-login.js"> </script> * Place <ea-login-form/> tag in to your html login page * Place <ea-logout-link/> tag in to your html page where the user has to click to logout */ (function() { 'use strict'; angular.module('ea-basic-auth-login', ['ea-base64-login']) .config(['$httpProvider', function ($httpProvider) { var ea_basic_auth_login_interceptor = ['$location', '$q', function($location, $q) { function success(response) { return response; } function error(response) { if(response.status === 401) { $location.path('/login'); return $q.reject(response); } else { return $q.reject(response); } } return function(promise) { return promise.then(success, error); } }]; $httpProvider.responseInterceptors.push(ea_basic_auth_login_interceptor); }]) .controller('EALoginCtrl', ['$scope','$http','$location','EABase64Login', function($scope, $http, $location, EABase64Login) { $scope.login = function() { $http.defaults.headers.common['Authorization'] = 'Basic ' + EABase64Login.encode($scope.email + ':' + $scope.password); $location.path("/user"); }; $scope.logout = function() { $http.defaults.headers.common['Authorization'] = undefined; $location.path("/login"); }; }]) .directive('eaLoginForm', [function() { return { restrict: 'E', template: '<div id="ea_login_container" ng-controller="EALoginCtrl">' + '<form id="ea_login_form" name="ea_login_form" novalidate>' + '<input id="ea_login_email_field" class="ea_login_field" type="text" name="email" ng-model="email" placeholder="E-Mail"/>' + '<br/>' + '<input id="ea_login_password_field" class="ea_login_field" type="password" name="password" ng-model="password" placeholder="Password"/>' + '<br/>' + '<button class="ea_login_button" ng-click="login()">Login</button>' + '</form>' + '</div>', replace: true }; }]) .directive('eaLogoutLink', [function() { return { restrict: 'E', template: '<a id="ea-logout-link" ng-controller="EALoginCtrl" ng-click="logout()">Logout</a>', replace: true } }]); angular.module('ea-base64-login', []). factory('EABase64Login', function() { var keyStr = 'ABCDEFGHIJKLMNOP' + 'QRSTUVWXYZabcdef' + 'ghijklmnopqrstuv' + 'wxyz0123456789+/' + '='; return { encode: function (input) { var output = ""; var chr1, chr2, chr3 = ""; var enc1, enc2, enc3, enc4 = ""; var i = 0; do { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); chr1 = chr2 = chr3 = ""; enc1 = enc2 = enc3 = enc4 = ""; } while (i < input.length); return output; }, decode: function (input) { var output = ""; var chr1, chr2, chr3 = ""; var enc1, enc2, enc3, enc4 = ""; var i = 0; // remove all characters that are not AZ, az, 0-9, +, /, or = var base64test = /[^A-Za-z0-9\+\/\=]/g; if (base64test.exec(input)) { alert("There were invalid base64 characters in the input text.\n" + "Valid base64 characters are AZ, az, 0-9, '+', '/',and '='\n" + "Expect errors in decoding."); } input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); do { enc1 = keyStr.indexOf(input.charAt(i++)); enc2 = keyStr.indexOf(input.charAt(i++)); enc3 = keyStr.indexOf(input.charAt(i++)); enc4 = keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 != 64) { output = output + String.fromCharCode(chr2); } if (enc4 != 64) { output = output + String.fromCharCode(chr3); } chr1 = chr2 = chr3 = ""; enc1 = enc2 = enc3 = enc4 = ""; } while (i < input.length); return output; } }; }); })();
可以使用angular-ui-routerredirect到另一个视图。 为此,我们有方法$state.go("target_view")
。 例如:
---- app.js ----- var app = angular.module('myApp', ['ui.router']); app.config(function ($stateProvider, $urlRouterProvider) { // Otherwise $urlRouterProvider.otherwise("/"); $stateProvider // Index will decide if redirects to Login or Dashboard view .state("index", { url: "" controller: 'index_controller' }) .state('dashboard', { url: "/dashboard", controller: 'dashboard_controller', templateUrl: "views/dashboard.html" }) .state('login', { url: "/login", controller: 'login_controller', templateUrl: "views/login.html" }); }); // Associate the $state variable with $rootScope in order to use it with any controller app.run(function ($rootScope, $state, $stateParams) { $rootScope.$state = $state; $rootScope.$stateParams = $stateParams; }); app.controller('index_controller', function ($scope, $log) { /* Check if the user is logged prior to use the next code */ if (!isLoggedUser) { $log.log("user not logged, redirecting to Login view"); // Redirect to Login view $scope.$state.go("login"); } else { // Redirect to dashboard view $scope.$state.go("dashboard"); } }); ----- HTML ----- <!DOCTYPE html> <html> <head> <title>My WebSite</title> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="description" content="MyContent"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="js/libs/angular.min.js" type="text/javascript"></script> <script src="js/libs/angular-ui-router.min.js" type="text/javascript"></script> <script src="js/app.js" type="text/javascript"></script> </head> <body ng-app="myApp"> <div ui-view></div> </body> </html>
在你的app.js文件中:
.run(["$rootScope", "$state", function($rootScope, $state) { $rootScope.$on('$locationChangeStart', function(event, next, current) { if (!$rootScope.loggedUser == null) { $state.go('home'); } }); }])
如果你不想使用angular-ui-router,但是想通过RequireJS让你的控制器延迟加载,那么在使用你的控制器作为RequireJS模块(延迟加载)时,事件$routeChangeStart
有一些问题。
你不能确定控制器会在$routeChangeStart
被触发之前被加载 – 事实上它不会被加载。 这意味着你不能像locals
或$$route
那样访问next
路线的属性,因为他们还没有设置。
例:
app.config(["$routeProvider", function($routeProvider) { $routeProvider.when("/foo", { controller: "Foo", resolve: { controller: ["$q", function($q) { var deferred = $q.defer(); require(["path/to/controller/Foo"], function(Foo) { // now controller is loaded deferred.resolve(); }); return deferred.promise; }] } }); }]); app.run(["$rootScope", function($rootScope) { $rootScope.$on("$routeChangeStart", function(event, next, current) { console.log(next.$$route, next.locals); // undefined, undefined }); }]);
这意味着你不能检查那里的访问权限。
解:
由于控制器的加载是通过解决scheme来完成的,所以您可以使用您的访问控制检查:
app.config(["$routeProvider", function($routeProvider) { $routeProvider.when("/foo", { controller: "Foo", resolve: { controller: ["$q", function($q) { var deferred = $q.defer(); require(["path/to/controller/Foo"], function(Foo) { // now controller is loaded deferred.resolve(); }); return deferred.promise; }], access: ["$q", function($q) { var deferred = $q.defer(); if (/* some logic to determine access is granted */) { deferred.resolve(); } else { deferred.reject("You have no access rights to go there"); } return deferred.promise; }], } }); }]); app.run(["$rootScope", function($rootScope) { $rootScope.$on("$routeChangeError", function(event, next, current, error) { console.log("Error: " + error); // "Error: You have no access rights to go there" }); }]);
注意在这里,而不是使用事件$routeChangeStart
我使用$routeChangeError
$routeProvider .when('/main' , {templateUrl: 'partials/main.html', controller: MainController}) .when('/login', {templateUrl: 'partials/login.html', controller: LoginController}). .when('/login', {templateUrl: 'partials/index.html', controller: IndexController}) .otherwise({redirectTo: '/index'});