angularjs - redireccionar - Redirigir a una determinada ruta en función de la condición
redireccionar angular 5 (11)
Estoy escribiendo una pequeña aplicación de AngularJS que tiene una vista de inicio de sesión y una vista principal, configurada así:
$routeProvider
.when(''/main'' , {templateUrl: ''partials/main.html'', controller: MainController})
.when(''/login'', {templateUrl: ''partials/login.html'', controller: LoginController})
.otherwise({redirectTo: ''/login''});
Mi LoginController comprueba la combinación de usuario / paso y establece una propiedad en $ rootScope que refleja esto:
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.";
}
}
Todo funciona, pero si accedo a http://localhost/#/main
, termino sin pasar por la pantalla de inicio de sesión. Quería escribir algo como "siempre que cambie la ruta, si $ rootScope.loggedUser es nulo, redirigir a / login"
...
... Espere. ¿Puedo escuchar los cambios de ruta de alguna manera? Voy a publicar esta pregunta de todos modos y seguir buscando.
1. Establecer usuario actual global.
En su servicio de autenticación, establezca el usuario autenticado actualmente en el ámbito raíz.
// AuthService.js
// auth successful
$rootScope.user = user
2. Establecer la función de autenticación en cada ruta protegida.
// AdminController.js
.config(function ($routeProvider) {
$routeProvider.when(''/admin'', {
controller: ''AdminController'',
auth: function (user) {
return user && user.isAdmin
}
})
})
3. Compruebe la autenticación en cada cambio de ruta.
// 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(''/'') }
}
})
})
Alternativamente, puede establecer permisos en el objeto de usuario y asignar a cada ruta un permiso, luego verificar el permiso en la devolución de llamada del evento.
Aquí es tal vez una solución más elegante y flexible con propiedades de configuración de "resolución" y "promesas" que permiten la carga eventual de datos en el enrutamiento y las reglas de enrutamiento en función de los datos.
Usted especifica una función en ''resolver'' en la configuración de enrutamiento y en la función cargar y verificar datos, hacer todas las redirecciones. Si necesita cargar datos, devuelve una promesa, si necesita redirigir, rechace la promesa antes de eso. Todos los detalles se pueden encontrar en las páginas de documentación de $routerProvider y $q .
''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;
}
};
Para la gente de habla rusa hay un post en habr " Вариант условного раутинга в AngularJS ".
Así es como lo hice, en caso de que ayude a alguien:
En la configuración, establezco un atributo publicAccess
en las pocas rutas que quiero abrir al público (como inicio de sesión o registro):
$routeProvider
.when(''/'', {
templateUrl: ''views/home.html'',
controller: ''HomeCtrl''
})
.when(''/login'', {
templateUrl: ''views/login.html'',
controller: ''LoginCtrl'',
publicAccess: true
})
luego, en un bloque de ejecución, configuro una escucha en el evento $routeChangeStart
que redirige a ''/login''
menos que el usuario tenga acceso o la ruta sea de acceso público:
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'');
}
});
})
Obviamente, podría cambiar la condición de isLoggedIn
a cualquier otra cosa ... simplemente mostrando otra forma de hacerlo.
Después de bucear a través de alguna documentación y código fuente, creo que lo tengo funcionando. ¿Quizás esto sea útil para alguien más?
Agregué lo siguiente a mi configuración de módulo:
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" );
}
}
});
})
Lo único que parece extraño es que tuve que probar el nombre parcial ( login.html
) porque el "siguiente" objeto de Ruta no tenía una url o algo más. Tal vez hay una mejor manera?
En su archivo app.js:
.run(["$rootScope", "$state", function($rootScope, $state) {
$rootScope.$on(''$locationChangeStart'', function(event, next, current) {
if (!$rootScope.loggedUser == null) {
$state.go(''home'');
}
});
}])
Es posible redirigir a otra vista con angular-ui-router . Para este propósito, tenemos el método $state.go("target_view")
. Por ejemplo:
---- 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>
He estado tratando de hacer lo mismo. Encontré otra solución más simple después de trabajar con un colega. Tengo un reloj configurado en $location.path()
. Eso hace el truco. Estoy empezando a aprender AngularJS y encuentro que esto es más limpio y legible.
$scope.$watch(function() { return $location.path(); }, function(newValue, oldValue){
if ($scope.loggedIn == false && newValue != ''/login''){
$location.path(''/login'');
}
});
Lo estoy haciendo utilizando interceptores. He creado un archivo de biblioteca que se puede agregar al archivo index.html. De esta manera, tendrá un manejo global de errores para sus llamadas de servicio de descanso y no tendrá que preocuparse por todos los errores individualmente. Más abajo también pegué mi biblioteca de inicio de sesión de autenticación básica. Allí puede ver que también verifico el error 401 y redirigir a una ubicación diferente. Ver 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
Casi lo mismo se puede hacer para el inicio de sesión. Aquí tiene la respuesta a la redirección ($ 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;
}
};
});
})();
Si no desea utilizar el enrutador ui angular, pero le gustaría que sus controladores se carguen de forma perezosa a través de RequireJS, hay un par de problemas con el evento $routeChangeStart
cuando usa sus controladores como módulos RequireJS (cargados de manera perezosa).
No puede estar seguro de que el controlador se cargará antes $routeChangeStart
; de hecho, no se cargará. Eso significa que no puede acceder a las propiedades de la next
ruta como los locals
o la $$route
porque aún no están configuradas.
Ejemplo:
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
});
}]);
Esto significa que no puede comprobar los derechos de acceso allí.
Solución:
Como la carga del controlador se realiza a través de la resolución, puede hacer lo mismo con su control de acceso:
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"
});
}]);
Tenga en cuenta que en lugar de usar el evento $routeChangeStart
, estoy usando $routeChangeError
Una forma diferente de implementar la redirección de inicio de sesión es usar eventos e interceptores como se describe aquí . El artículo describe algunas ventajas adicionales, como detectar cuándo se requiere un inicio de sesión, poner en cola las solicitudes y reproducirlas una vez que el inicio de sesión sea exitoso.
Puede probar una demostración de trabajo here y ver la fuente de demostración here .
$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''});