【问题标题】:Redirecting to a certain route based on condition根据条件重定向到某个路由
【发布时间】:2019-09-21 09:37:33
【问题描述】:

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

$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,我最终会绕过登录屏幕。我想写类似“每当路由发生变化时,如果 $rootScope.loggedUser 为空,则重定向到 /login”

...

...等等。我可以以某种方式收听路由更改吗?无论如何我都会发布这个问题并继续寻找。

【问题讨论】:

  • 澄清一下:虽然下面的许多解决方案运行良好,但我最近更倾向于接受@Oran 下面的回答——也就是说,当被要求提供敏感信息时,让服务器以 401 代码响应URL,并使用该信息来控制客户端上的“登录框”。 (但是,陪审团仍然在“排队拒绝请求并稍后重新发出”位,至少对我来说:))

标签: angularjs ngroute


【解决方案1】:

在浏览了一些文档和源代码之后,我想我已经搞定了。也许这对其他人有用?

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

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),因为“下一个”Route 对象没有 url 或其他东西。也许有更好的方法?

【讨论】:

  • 酷哥,感谢您分享您的解决方案。需要注意的一点:在当前版本中,它是“next.$route.templateUrl”
  • 如果您在 chrome 检查器中查看网络请求,仍然会调用被重定向的路由(因为用户未登录)并将响应发送到浏览器,然后重定向路径'/login' 被调用。所以这种方法不好,因为未登录的用户可以看到他们不应该访问的路由的响应。
  • 使用 $locationChangeStart 而不是 $routeChangeStart 来防止路由被调用并让未经身份验证的用户查看他们不应访问的内容。
  • 记住这是客户端。还应该有一个服务器端屏障。
  • @sonicboom $locationChangeStart 如果不是所有路由都需要身份验证,则没有意义,使用 $routeChangeStart 您可以在路由对象上获得元数据,例如是否经过身份验证或需要哪些角色那条路线。您的服务器应该处理不显示未经身份验证的内容,并且 AngularJS 在路由更改之前不会开始处理,因此不应显示任何内容。
【解决方案2】:

实现登录重定向的另一种方式是使用事件和拦截器作为described here。本文介绍了一些额外的优势,例如检测何时需要登录、将请求排队以及在登录成功后重放它们。

您可以试用一个工作演示 here 并查看演示源 here

【讨论】:

  • 您能否更新此答案以包含链接中的相关信息?这样,即使链接断开,它也将继续对访问者有用。
【解决方案3】:

我一直在尝试做同样的事情。在与同事合作后想出了另一个更简单的解决方案。我在$location.path() 上设置了一块手表。这就是诀窍。我刚开始学习 AngularJS,发现它更干净、更易读。

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

【讨论】:

  • 这看起来很有趣。您能否在某处发布示例?
  • 你在哪里设置手表?
  • @freakTheMighty 您必须在 mainCtrl 函数中设置手表,其中 ng-controller 设置为 mainCtrl。例如
  • 我认为,如果投反对票,应该有理由发表评论,这是公平的。它将作为一种学习工具有所帮助。
【解决方案4】:

我正在使用拦截器。我创建了一个可以添加到 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;
            }
        };
    });
})();

【讨论】:

  • 除非你在指令中,否则你真的应该远离在 JS 中进行 dom 操作。如果你只是设置了你的逻辑,然后使用 ng-class 来应用一个类并触发一个 CSS 动画,你以后会感谢你自己的。
【解决方案5】:

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

您在路由配置中的“resolve”中指定一个函数,并在函数加载和检查数据中,执行所有重定向。如果您需要加载数据,则返回一个承诺,如果您需要重定向 - 在此之前拒绝承诺。 所有详细信息都可以在 $routerProvider$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;
    }
};

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

【讨论】:

  • 为什么checkRouting函数映射到工厂?映射到什么重要吗?
  • @honkskillet:来自角度 $routeProvider 文档:“factory - {string|function}:如果是字符串,则它是服务的别名。否则,如果是函数,则将其注入并返回值被视为依赖项。如果结果是一个承诺,它会在其值注入控制器之前被解析。请注意,ngRoute.$routeParams 仍将引用这些解析函数中的先前路由。使用 $route.current。 params 来访问新的路由参数,而不是。”同样来自有关解决的文档:“如果任何承诺被拒绝,则触发 $routeChangeError 事件。”
  • 如果使用ui.router,请使用$stateProvider而不是$routeProvider
【解决方案6】:

我是这样做的,以防它帮助任何人:

在配置中,我在几条我想向公众开放的路由(例如登录或注册)上设置了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 更改为其他任何内容...只是显示另一种方法。

【讨论】:

  • 你的运行块参数中的用户是什么?服务?
  • 是的,它是一个负责检查 cookie 等以查看用户是否登录的服务。
  • 你可以像nextLoc.$$route.publicAccess btw这样访问路由。
  • 或者使用$route.routes[nextLoc.originalPath],不使用私有变量。
  • 其实你可以查nextLoc &amp;&amp; nextLoc.publicAccess!
【解决方案7】:

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

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

【讨论】:

  • @malcolmhall 是的,这是选择加入,您想选择退出。而是将“公共”布尔值添加到登录页面等公共路由,并重定向 if (!user &amp;&amp; !next.$$route.public)
  • 有人能给我解释一下next.$$route吗?我在 Angular 文档中找不到任何描述给$routeChangeStart 事件的参数,但我认为nextcurr 是某种位置对象? $$route 位很难用谷歌搜索。
  • 我现在看到$$route 属性是Angular 的一个私有变量。你不应该依赖它,例如:stackoverflow.com/a/19338518/1132101 - 如果你这样做,你的代码可能会在 Angular 更改时中断。
  • 我找到了一种无需访问私有财产或必须循环通过$route.routes 来构建列表的方法(如@thataustin 的回答):获取该位置的路径next.originalPath 并使用它来索引 $route.routes: var auth = $route.routes[next.originalPath]
  • 至于回答我之前三个 cmets 关于事件的论点的问题 - 它们似乎确实没有记录,请参阅这个问题,它也恰好引用了这个 SO 问题:github.com/angular/angular.js/issues/10994
【解决方案8】:

可以使用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>

【讨论】:

    【解决方案9】:
        $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'});
    

    【讨论】:

    • 这是一个基本的路由配置......在重定向到配置的路由之前,我们在哪里检查任何条件......?
    【解决方案10】:

    如果您不想使用 angular-ui-router,但希望通过 RequireJS 延迟加载控制器,则在将控制器用作 RequireJS 模块(延迟加载)时,事件 $routeChangeStart 会出现一些问题。

    你不能确定控制器会在$routeChangeStart 被触发之前被加载——事实上它不会被加载。这意味着您无法访问 next 路由的属性,例如 locals$$route,因为它们尚未设置。
    示例:

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

    这意味着您无法在其中检查访问权限。

    解决方案:

    由于控制器的加载是通过解析完成的,因此您可以对访问控制检查进行同样的操作:

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

    请注意,我使用的是$routeChangeError,而不是使用事件$routeChangeStart

    【讨论】:

      【解决方案11】:

      在您的 app.js 文件中:

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

      【讨论】:

        猜你喜欢
        • 2017-04-17
        • 2021-08-10
        • 1970-01-01
        • 2021-11-07
        • 2018-10-22
        • 1970-01-01
        • 1970-01-01
        • 2020-12-18
        相关资源
        最近更新 更多