AngularJS,ui.router,基于用户角色的加载模板和控制器

时间:2014-05-02 13:14:18

标签: angularjs templates controller angular-ui-router user-roles

我开发了一个使用REST api的单页应用程序。用户需要登录才能访问该应用程序。当用户登录时,他们将被重定向到/ dashboard。在此网址/路线上,我想根据用户的角色(例如,普通用户管理员用户)加载不同的模板和控制器。

我查看了模板部分下的https://github.com/angular-ui/ui-router/wiki,但没有一个选项支持我想要实现的目标。

  • 通过使用templateUrl和function(stateParams),我无法注入帮助我确定用户角色的服务,以便我可以加载模板,例如views / 用户 /dashboard.html或观看/ 管理员 /dashboard.html
  • 通过使用templateProvider,我将注入帮助我确定用户角色的服务,但是如何加载模板?

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

所以我需要的是根据用户登录时在服务中设置的用户角色变量加载不同模板和控制器的单一路由。

我是在考虑正确的思路,还是应该实施其他解决方案?

对此的任何帮助将不胜感激。

9 个答案:

答案 0 :(得分:23)

基于用户角色加载模板和控制器

虽然技术上ui-router templateUrl函数不支持注入服务,但您可以使用templateProvider注入保存service变量的role或者异步加载它,然后使用{{1返回HTML内容。请考虑以下示例:

$templateFactory

对于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选项注入{{1>} 已经由controllerProvider 解析service。请查看以下role州定义中的templateProvider选项示例:

controllerProvider

当然,您可以轻松地从此代码中删除重复项,并定义更易于访问的微型DSL,以便更轻松地为特定角色和视图定义不同的规则。

以下demo应该可以帮助您理解代码。

这是一种正确的方法吗?

通常这很大程度上取决于背景。为了帮助提出答案,请先提出以下问题:

  • 向角色提供的观看次数有多大差异?

您是否只隐藏几个ui-router和其他操作元素,基本上只为常规用户设置页面并为超级用户编辑?如果更改很小,我可能会使用相同的视图并且只隐藏特定元素,可能伪造类似于controllerProvider: function(session){ if(session.role == 'admin'){ return 'AdminCtrl'; } else { return 'UserCtrl'; } } 的指令,允许以声明方式button启用/禁用特定功能。另一方面,如果视图将会大不相同,那么使用不同的模板可以大大简化标记。

  • 特定上有多少操作因角色而异?

表面上看起来相似的行为在不同角色的内部工作方面是否有所不同?例如,如果您有编辑操作同时适用于ng-ifonly-role='operator, admin'角色,但在一种情况下,它会启动向导(如UI)和其他复杂功能高级用户的表单然后单独user更有意义。另一方面,如果admin操作是controller操作的超集,则单个控制器似乎更容易遵循。 请注意,在这两种情况下保持admin都能得到回报 - 他们应该只将视图粘贴到封装在服务/视图模型/模型中的行为/选择名称

  • 您是否会从应用的不同位置通过特定的页面获得许多上下文单独的链接

例如,只要编写user,无论当前用户controller是否存在于各种上下文中,都可以提供导航到特定页面。如果是这种情况,那么在单一路由/状态下定义它们似乎更易于维护,那么用于基于角色构建不同ui-sref="dashboard" / role的条件逻辑。但是,您也可以根据用户角色动态定义路由/状态 - 动态加载或不加载

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

有时我们首先为普通用户构建功能,然后是高级版,然后是最终用户。在团队成员之间划分ui-srefng-href页面上的工作并不罕见,特别是如果可以轻松绘制清晰的边界。在这种情况下,单独的useradmin可以让开发人员避免冲突。当然,不是所有的彩虹和独角兽 - 团队必须非常自律,以消除重复,这很可能会发生。

希望我的建议可以帮助你做出决定。

答案 1 :(得分:15)

  

我是在思考正确的方向,还是应该实施   另一个解决方案?

IMO,你不应该这样做。

在此,我提出了另外两种解决方案,具体取决于您的应用程序的实现方式。

1)如果您的角色权限可以配置(您可以有一个单独的页面来配置您的角色,为您的角色分配权限,......)。然后,只为您的角色使用1个模板和1个控制器(普通用户,管理员用户等......)并使用ng-showng-class,..来相应地显示您的HTML。

在这种情况下,我们不关心用户是普通用户还是管理员用户,这只是我们角色的名称。我们关心的是权利,它是动态=>因此,我们应该根据配置的权限动态显示html(当然,当用户执行操作以防止用户制作恶意http请求并发布到服务器时,也会检查服务器端)。如果我们使用单独的模板,则会有无数个案。

此解决方案的重点是页面的功能与您的角色相同,您只需要显示/隐藏基于页面的功能用户。

2)如果角色的权限已修复(无法配置)且普通用户和管理员用户的视图的功能不同。最好对这些视图使用单独的状态,并根据登录用户授权访问这些视图(当然,当用户执行操作时,服务器端也有授权)。

原因是:管理员用户视图和普通用户视图具有不同功能(应该相互分离)

答案 2 :(得分:7)

如果您使用的角度大于1.2,则可以使用templateUrl作为函数执行指令。

所以基本的想法是你有一个仪表板视图,它有一个自定义指令,它将根据用户级别确定模板。所以像这样:

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

答案 3 :(得分:4)

我。 使用“...加载不同模板的单一路线......”,这是我的建议,我的回答。

如果可能的话:

  

尝试退后一步,重新考虑整个设计和   尝试削弱我们的应用用户对url 感兴趣的感觉

他们不是。如果他们确实理解了什么是url地址栏 ......他们会将其用于copysendpaste ......不要调查它的部分...

II。建议:Enforce the usage of the ui-router

  

... UI-Router按州组织,可能 OPTIONALLY 包含路线以及附加的其他行为......

这意味着,让我们重新考虑我们的应用程序作为明确定义的状态的组/层次结构。他们可以定义url但不必 (例如error state没有网址)

III。我们如何从各州建立应用程序中获利?

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

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

这意味着,可能会有更多状态重复使用视图控制器等。此类状态可以真的不同(相同的视图,不同的控制器)。但它们是单一目的 - 它们可以处理某些情况:

  • 管理用户/ Emplyoee记录
  • 用户/员工列表 - 信息ala PhoneList(只是电子邮件,电话......)
  • 安全管理 - 用户的权利是什么 ...

同样,可能有许多。甚至有一百个州都不会出现性能问题。这些只是定义,是对其他部分的一组引用,应该使用......稍后......如果真的需要。

一旦我们在级别定义了用例用户故事,我们就可以将它们分组到集合/层次结构中。
这些组可以稍后以不同的格式(不同的菜单项)呈现给不同的用户角色

但最后,我们获得了很多自由和简化的可维护性

IV。让应用程序继续运行并且不断发展

如果州很少,维护不会出现问题。但它可能会发生,应用程序将成功。成功......并在其设计中成长。

分割状态定义(作为工作单元)及其层次结构(用户角色可以访问哪些状态)将简化其管理。

在状态(事件监听器ala '$stateChangeStart'之外应用安全性要容易得多,然后永远不会结束模板提供者的重构。此外,安全性的主要部分应该仍然应用在服务器上,无论UI允许什么

V。摘要:

虽然有templateProvider这样的强大功能,可以为我们做一些有趣的事情(例如:Changing Navigation Menu using UI-Router in AngularJs)...

......我们不应该将它用于安全性。这可以实现为基于当前角色从现有状态构建的一些菜单/层次结构。事件侦听器应检查用户是否已进入授予状态,但必须在服务器上应用主检查...

答案 4 :(得分:3)

我使用了以下解决方案(这可能不太理想,但在这种情况下它对我有用):

  1. 使用ngController

  2. 在模板中指定控制器
  3. 使用通用视图名称加载模板(例如views/dashboard.html)。

  4. 每当登录的用户角色发生变化时,使用views/dashboard.html更改$templateCache.put(...)引用的内容。


  5. 以下是该方法的一个简单示例:

    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服务完成,因此不会产生任何差异。)

答案 5 :(得分:3)

你真的不需要使用路由器。

最简单的方法是为所有角色使用一个模板,并在其中使用动态ng-include。假设您在$ scope中有注入器:

<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>

答案 6 :(得分:1)

这里不需要长篇解释。

使用resolve并更改$ route。$$ route.templateUrl,或者通过将新路由或相关参数传递给promise来使用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();
      }
    });
});

答案 7 :(得分:1)

我知道这已经过了一段时间,因为这个问题已经发布但是我正在添加我的答案,因为我使用的方法与其他答案不同。

在这种方法中,我完全根据用户的角色分离路径和模板URL,并将用户重定向到索引页面,如果它们位于无权查看的路径中。

使用UI路由器,我基本上将这样的数据属性添加到状态:

.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;
                 }
     }

});

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

答案 8 :(得分:0)

有一个很棒的项目https://github.com/Narzerus/angular-permission它需要ui-router。该项目是新的,但仍然运作良好。