根据条件重定向到特定的路由

我正在写一个小的AngularJS应用程序,它有一个登录视图和一个主视图,配置如下:

$routeProvider
.when('/main' , {templateUrl: 'partials/main.html',  controller: MainController})
.when('/login', {templateUrl: 'partials/login.html', controller: LoginController})
.otherwise({redirectTo: '/login'});

我的LoginController检查user/pass组合,并在$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,我最终会绕过登录屏幕。我想写一些类似于“每当路由改变,如果$rootScope。loggedUser为空,然后重定向到/login"

...

... 等待。我能以某种方式听到路线变化吗?不管怎样,我会把这个问题发布出来,继续寻找。

484345 次浏览

在深入研究了一些文档和源代码之后,我想我已经让它工作了。也许这对其他人有用?

我在我的模块配置中添加了以下内容:

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" ) {
// not going to #login, we should redirect now
$location.path( "/login" );
}
}
});
})

有一件事似乎很奇怪,我必须测试部分名称(login.html),因为“next”Route对象没有url或其他东西。也许有更好的办法?

实现登录重定向的另一种方式是使用事件和拦截器,如这里描述。本文描述了一些额外的优点,例如检测何时需要登录、排队请求以及在登录成功后重新播放它们。

你可以尝试一个工作的演示在这里,并查看演示源代码在这里

我也一直在努力这么做。在和同事一起工作后,我想出了另一个更简单的解决方案。我有一个手表设置在$location.path()。这很管用。我刚开始学习AngularJS,发现这是更干净和可读的。

$scope.$watch(function() { return $location.path(); }, function(newValue, oldValue){
if ($scope.loggedIn == false && newValue != '/login'){
$location.path('/login');
}
});

我用的是拦截器。我已经创建了一个库文件,可以添加到index.html文件。这样,您就可以对其余服务调用进行全局错误处理,而不必单独关心所有错误。再往下,我还粘贴了我的基本认证登录库。在这里,您可以看到我还检查了401错误并重定向到不同的位置。看到lib / ea-basic-auth-login.js

lib / http-error-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-error-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-basic-auth-login.js

几乎同样可以用于登录。这里有重定向的答案($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 A-Z, a-z, 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 A-Z, a-z, 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;
}
};
});
})();

这里可能有一个更优雅、更灵活的解决方案,它具有“resolve”配置属性和“promises”,可以根据数据在路由和路由规则上最终加载数据。

你在路由配置中的“resolve”和函数加载和检查数据中指定了一个函数,执行所有重定向。如果你需要加载数据,你返回一个承诺,如果你需要重定向-拒绝承诺之前。 所有细节可以在routerProvider美元美元的问文档页中找到
'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;
}
};

对于讲俄语的人,有一个关于habr“Вариант сл лов о о о о о о о ин а а в AngularJS”的帖子。

以下是我的做法,希望对大家有所帮助:

在配置中,我在我想向公众开放的少数路由(如登录或注册)上设置了publicAccess属性:

$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
})
.when('/login', {
templateUrl: 'views/login.html',
controller: 'LoginCtrl',
publicAccess: true
})

然后在一个运行块中,我在$routeChangeStart事件上设置了一个侦听器,它重定向到'/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更改为其他任何条件…只是展示了另一种方法。

1. 设置全局当前用户。

在身份验证服务中,将当前经过身份验证的用户设置为根作用域。

// AuthService.js


// auth successful
$rootScope.user = user

2. 在每条受保护路由上设置认证功能。

// 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('/') }
}
})
})

或者,您可以在用户对象上设置权限并为每个路由分配权限,然后在事件回调中检查权限。

可以用angular-ui-router重定向到另一个视图。为此,我们有$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>
    $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'});

如果你不想使用angular-ui-router,但想让你的控制器通过RequireJS惰性加载,当你使用控制器作为RequireJS模块(惰性加载)时,事件$routeChangeStart会有几个问题。

你不能确定控制器会在$routeChangeStart被触发之前被加载——事实上它不会被加载。这意味着你不能访问next路由的属性,如locals$$route,因为它们还没有设置 例子:< / p >
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
});
}]);

这意味着您不能检查其中的访问权限。

解决方案:

由于控制器的加载是通过resolve完成的,你可以对你的访问控制检查做同样的事情:

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

在你的app.js文件中:

.run(["$rootScope", "$state", function($rootScope, $state) {


$rootScope.$on('$locationChangeStart', function(event, next, current) {
if (!$rootScope.loggedUser == null) {
$state.go('home');
}
});
}])