AngularJS、ui.路由器、加载模板和基于用户角色的控制器

webghufk  于 2023-02-11  发布在  Angular
关注(0)|答案(9)|浏览(121)

我开发了一个使用REST API的单页应用程序。用户需要登录才能访问该应用程序。当用户登录时,他们将被重定向到/dashboard。在此URL/route上,我希望根据用户的角色(例如,普通用户管理员用户)加载不同的模板和控制器。
我已经在模板部分下查看了https://github.com/angular-ui/ui-router/wiki,但是没有一个选项支持我试图实现的目标。

  • 通过使用templateUrl和函数(stateParams),我无法 * 注入 * 帮助我确定用户角色的服务,以便加载模板,例如views/user/dashboard. html或views/admin/dashboard. html
  • 通过使用templateProvider,我将注入帮助我确定用户角色的服务,但是我如何加载模板呢?

任何解决方案都应该根据用户角色加载不同的控制器,例如UserDashboardController或AdminDashboardController。

  • 因此,基本上我需要的是基于用户登录时在服务中设置的用户角色变量加载不同模板和控制器的单个路由。*

我的思路是否正确,还是应该实施另一种解决方案?
在这方面的任何帮助将不胜感激。

uelo1irk

uelo1irk1#

根据用户角色加载模板和控制器

虽然从技术上讲ui-router templateUrl函数不支持注入服务,但您可以使用templateProvider注入保存role变量的service或异步加载它,然后使用$templateFactory返回HTML内容。

var app = angular.module('app', ['ui.router']);

app.service('session', function($timeout, $q){
    this.role = null;

    this.loadRole = function(){
        //load role using axax request and return promise
    };
});

app.config(function($stateProvider, $urlRouterProvider){
    $stateProvider.state('dashboard', {
        url: '/dashboard',
        templateProvider: function(session, $stateParams, $templateFactory){
          return session.loadRole().then(function(role){
              if(session.role == 'admin'){
                return $templateFactory.fromUrl('/admin/dashboard.html', $stateParams);
              } else {
                return $templateFactory.fromUrl('/user/dashboard.html', $stateParams);
              }
          });
        }
      });

    $urlRouterProvider.otherwise('/dashboard');
});

对于controller,您可以声明您希望使用ng-controller的每个模板根元素内的特定控制器。或者类似地,您可以使用controllerProvider选项注入service,**将已经通过templateProvider**解析role。请看ui-router状态定义中controllerProvider选项的以下示例:

controllerProvider: function(session){
  if(session.role == 'admin'){
    return 'AdminCtrl';
  } else {
    return 'UserCtrl';  
  }
}

当然,您可以很容易地删除这些代码中的重复部分,并定义一个更易于访问的微型DSL,以便更容易地为特定角色和视图定义不同的规则。
下面的demo应该有助于您理解代码。

这是正确的做法吗?

通常情况下,这在很大程度上取决于上下文。为了帮助得出答案,让我先提出以下问题:

  • 呈现给角色的视图有多大差异?

你打算只隐藏几个button和其他动作元素,基本上是让一个页面只对普通用户可读,对超级用户可编辑吗?如果修改很小,我可能会使用相同的视图,只隐藏特定的元素。可能伪造类似于ng-if的指令,该指令将允许声明性地启用/禁用特定功能only-role='operator, admin'。另一方面,如果视图有很大的不同,那么使用不同的模板可以大大简化标记。

  • 特定 * 页面 * 上可用的操作因角色而异?

表面上看起来相似的操作在不同角色的内部工作方式中是否不同?例如,如果您有 Edit 操作可用于useradmin角色,但在一种情况下,它启动了 * 向导 * 一样的UI,在其他情况下,为高级用户提供了一个复杂的表单,然后有一个单独的controller更有意义。另一方面,如果admin操作是user操作的超集,那么使用单个控制器似乎更容易遵循。* 请注意,在这两种情况下,保留controller都是有好处的--它们应该只将视图粘合到封装在服务/视图模型/模型/选择名称中的行为 *

  • 你会有许多上下文独立的 * 链接 * 从应用程序的不同地方指向特定的 * 页面 * 吗?

例如,能够通过简单地写入ui-sref="dashboard"而不管当前用户role来提供到特定 * 页面 * 的导航,如果它存在于各种上下文中,则可能是有益的。如果是这种情况,则将它们定义在单个路由/状态下似乎比用于基于角色构建不同ui-sref/ng-href的条件逻辑更易于维护。但是,您也可以根据用户角色动态定义路线/州(动态加载或非动态加载)

  • 特定 * 页面 * 上不同角色可用的视图和操作是单独发展还是一起发展?

有时候,我们首先为普通用户开发特性,然后是高级用户,最后是终极用户。团队成员之间在useradmin页面上分工并不罕见,特别是如果可以很容易地画出清晰的界限。在这种情况下,将viewscontrollers分开可以简化开发人员的工作,避免冲突。当然,这并不全是彩虹和独角兽-团队必须严格遵守纪律,以消除最有可能发生的重复。
希望我的建议能帮助你做出决定。

rjzwgtxy

rjzwgtxy2#

我的思路是否正确,还是应该实施另一种解决方案?
IMO,你不应该这样做。
在这里,我根据应用程序的实现方式提出了另外两种解决方案。
1)如果您的角色权限可以配置(您可以使用单独的页面来配置您的角色,为您的角色分配权限,...)。然后仅使用1个模板和1个控制器来配置您的角色(普通用户,管理员用户,等等...),并使用ng-showng-class,..来相应地显示您的HTML。
在这个例子中,我们并不关心用户是普通用户还是管理员用户,这只是我们角色的名称,我们关心的是权限,而且是动态的=〉因此,我们应该根据配置的权限动态显示html(当然,当用户执行动作以防止用户手工制作恶意HTTP请求并向服务器发帖时,在服务器侧也存在检查)。如果我们为此使用单独的模板,则会有无数的情况。
这个解决方案的要点是页面的功能与您的角色相同,您只需要根据用户显示/隐藏页面的功能。
2)如果角色的权限是固定的(无法配置),并且普通用户和管理员用户的视图的功能不同,那么最好为这些视图使用单独的状态,并根据登录用户授权访问这些视图(当然,当用户执行某个操作时,服务器端也有授权)。
原因是:管理员用户视图和普通用户视图具有不同的功能(应该彼此分开)

mw3dktmi

mw3dktmi3#

如果您使用的是大于1.2的angular版本,则可以使用templateUrl作为函数执行指令。
基本思想是,您有一个 Jmeter 板视图,其中包含一个自定义指令,该指令将根据用户级别确定模板。

(function () {
  'use strict';
  angular.module('App.Directives')
    .directive('appDashboard', ['UserManager', function (UserManager) {
      return {
        restrict: 'EA',
        templateUrl: function(ele, attr){
            if (UserManager.currentUser.isAdmin){
                return 'admin.html';
            }else{
                return 'user.html';
            }
        }
      };
    }]);
})();
mf98qq94

mf98qq944#

**I.不要使用 "...加载不同模板的单一路径...",这将是我的建议,我的答案。

如有可能:
试着退后一步重新考虑整个设计
尝试削弱我们的应用程序**用户对url*感兴趣的感觉。
他们不是。而且如果他们真的了解什么是url
地址栏 *...他们用它来copysendpaste...而不是调查它的部件...

二.建议:强制使用ui路由器说明

... UI-Router围绕状态进行组织,这些状态可以可选地附加路由以及其他行为...
这意味着,让我们将应用程序视为定义良好的状态的组/层次结构。它们可以定义url但不必*(例如,error state没有url)*

**III.**我们如何从在各州构建应用程序中获益?

分离关注-应该是我们的目标。

状态是一个收集一些 * 视图 / 控制器 解析器 自定义数据 *...

这意味着,可能有更多的状态重用 * 视图 控制器 * 等。这样的状态可能真的不同 (相同的视图,不同的控制器)。但它们的用途是单一的--它们在那里处理一些场景:

  • 用户/雇员记录管理
  • 用户/员工列表-电话列表中的信息(仅电子邮件、电话...)
  • 安全管理-用户的权限是什么...

同样,可能有很多很多的状态。即使有一百个状态也不会是性能问题。这些只是定义,一组对其他部分的引用,应该在以后使用,如果真的需要的话。
一旦我们在状态的级别上定义了 * 用例 用户故事 *,我们就可以将它们分组到集合/层次结构中。
这些组可以稍后以不同格式(不同菜单项)呈现给不同用户角色
但最终,我们获得了很大的自由度和简化的维护

**IV.**保持应用程序运行 * 和增长 *

如果有几个状态,维护似乎不是问题。但它可能发生,应用程序将成功。成功和成长...在其设计内部。
将状态定义(作为一个工作单元)和它们的层次结构(哪个用户角色可以访问哪个状态)分开可以简化它的管理。
在状态之外应用安全性 (事件监听器alax 1 m7n1x) 要容易得多,然后永远不要结束模板提供程序的重构。此外,安全性的主要部分仍然应该应用在服务器上,无论UI允许什么

**五.**摘要:

虽然templateProvider是一个很棒的特性,但它可以为我们做一些有趣的事情(例如:Changing Navigation Menu using UI-Router in AngularJs)...
...我们不应将其用于安全性。这可以实现为基于当前角色从现有状态构建的一些菜单/层次结构。事件侦听器应检查用户是否进入授权状态,但主要检查必须应用于服务器...

pzfprimi

pzfprimi5#

我采用了下面的解决方案(这可能并不理想,但它在此类场景中对我很有效):
1.使用ngController在模板本身中指定控制器。
1.使用通用视图名称(例如views/dashboard.html)加载模板。
1.每当登录的用户角色发生更改时,使用$templateCache.put(...)更改views/dashboard.html所指的内容。
下面是该方法的一个简化示例:

app.controller('loginCtrl', function ($location, $scope, User) {
    ...
    $scope.loginAs = function (role) {
        // First set the user role
        User.setRole(role);

        // Then navigate to Dashboard
        $location.path('/dashboard');
    };
});

// A simplified `User` service that takes care of swapping templates,
// based on the role. ("User" is probably not the best name...)
app.service('User', function ($http, $templateCache) {
    var guestRole = 'guest';
    var facadeUrl = 'views/dashboard.html';
    var emptyTmpl = '';
    var errorTmpl = 'Failed to load template !';
    var tempTmpl  = 'Loading template...';

    ...

    // Upon logout, put an empty template into `$templateCache`
    this.logout = function () {
        this.role = guestRole;
        $templateCache.put(facadeUrl, emptyTmpl);
    };

    // When the role changes (e.g. upon login), set the role as well as the template
    // (remember that the template itself will specify the appropriate controller) 
    this.setRole = function (role) {
        this.role = role;

        // The actual template URL    
        var url = 'views/' + role + '/dashboard.html';

        // Put a temporary template into `$templateCache`
        $templateCache.put(facadeUrl, tempTmpl);

        // Fetch the actual template (from the `$templateCahce` if available)
        // and store it under the "generic" URL (`views/dashboard.html`)
        $http.get(url, {cache: $templateCache}).
              success(function (tmpl) {
                  $templateCache.put(facadeUrl, tmpl);
              }).
              error(function () {
                  // Handle errors...
                  $templateCache.put(facadeUrl, errorTmpl);
              });
    };

    // Initialize role and template        
    this.logout();
});

// When the user navigates to '/dashboard', load the `views/dashboard.html` template.
// In a real app, you should of course verify that the user is logged in etc...
// (Here I use `ngRoute` for simplicity, but you can use any routing module.)
app.config(function ($routeProvider) {
    $routeProvider.
        when('/dashboard', {
            templateUrl: 'views/dashboard.html'
        }).
        ...
});

另请参见此**short demo
(我使用ngRoute是为了简单起见,但它没有
任何**区别,因为所有工作都是由User服务完成的。)

ukxgm1gy

ukxgm1gy6#

你真的不需要用路由器。
最简单的方法是为所有角色使用一个模板,并在其中使用动态ng-include。假设$scope中有injector:

<div ng-include="injector.get('session').role+'_dashboard.html'"></div>

所以你应该有user_dashboard.htmladmin_dashboard.html视图,在每个视图里面你可以应用单独的控制器,例如user_dashboard.html

<div id="user_dashboard" ng-controller="UserDashboardCtrl">
    User markup
</div>
nhjlsmyf

nhjlsmyf7#

这里不需要长篇大论。
使用resolve并更改$route.$$route.templateUrl,或者通过将新路由或相关参数传递给承诺来使用routeChangeError。

var md = angular.module('mymodule', ['ngRoute']);
md.config(function($routeProvider, $locationProvider) {
    $routeProvider.when('/common_route/:someparam', {
        resolve: {
            nextRoute: function($q, $route, userService) {
                defer = $q.defer()
                userService.currentRole(function(data) { defer.reject({nextRoute: 'user_based_route/'+data) });
                return defer.promise;
            }
        }
    });
    $rootScope.$on("$routeChangeError", function(evt, current, previous, rejection) {
      if (rejection.route) {
        return $location.path(rejection.route).replace();
      }
    });
});
piv4azn7

piv4azn78#

我知道这已经有一段时间以来,这个问题被张贴,但我添加我的答案,因为我的方法,我使用的是不同于其他答案在这里。
在这个方法中,我完全根据用户的角色来分离路由和模板url,如果用户在一个他们没有被授权查看的路由中,我会将用户重定向到索引页面。
使用UI Router,我基本上向状态添加了一个如下所示的数据属性:

.state('admin', {
            url: "/admin",
            templateUrl: "views/admin.html",
            data: {  requireRole: 'admin' }
        })

当用户通过身份验证时,我将他们的角色数据从控制器存储到localstorage$rootscope中,如下所示:

var role = JSON.stringify(response.data); // response from api with role details

// Set the stringified user data into local storage
localStorage.setItem('role', role);

// Putting the user's role on $rootScope for access by other controllers
$rootScope.role = response.data;

最后,我使用$stateChangeStart检查角色,如果用户不应该查看页面,则重定向用户:

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

        // $stateChangeStart is fired whenever the state changes. We can use some parameters
        // such as toState to hook into details about the state as it is changing
        $rootScope.$on('$stateChangeStart', function(event, toState) {

                var role = JSON.parse(localStorage.getItem('role'));
                $rootScope.role = role;

                // Redirect user is NOT authenticated and accesing private pages
                var requireRole = toState.data !== undefined
                                  && toState.data.requireRole;

                 if( (requireRole == 'admin' && role != 'admin')) )
                 {
                   $state.go('index');
                   event.preventDefault();
                   return;
                 }
     }

});

除此之外,在向用户显示任何数据之前,您仍然需要执行服务器端授权检查。

fv2wmkja

fv2wmkja9#

有一个优秀的项目https://github.com/Narzerus/angular-permission它需要ui路由器。该项目是新的,但工作得很好。

相关问题