web-dev-qa-db-ja.com

AngularJS、ui.router、ユーザーロールに基づいたテンプレートとコントローラーのロード

REST api。ユーザーはアプリケーションにアクセスするためにログインする必要があります。ユーザーがログインすると、/ dashboardにリダイレクトされます。ユーザーの役割に基づいて異なるテンプレートとコントローラーをロードしたい(例:normaluserまたはadmin ユーザー)。

私は https://github.com/angular-ui/ui-router/wiki をテンプレートセクションの下で見ましたが、どのオプションも私が達成しようとしていることをサポートしていません。

  • TemplateUrlと関数(stateParams)を使用することにより、テンプレートをロードできるようにユーザーロールを決定するのに役立つサービスinjectができません。 views /user/ dashboard.htmlまたはviews /admin/ dashboard html
  • TemplateProviderを使用して、ユーザーロールを決定するのに役立つサービスを注入しますが、テンプレートをロードするにはどうすればよいですか?

また、すべてのソリューションは、UserDashboardControllerやAdminDashboardControllerなど、ユーザーロールに基づいて異なるコントローラーをロードする必要があります。

だから、私が必要なのは、ユーザーがログインしたときにサービスで設定されるユーザーロール変数に基づいて、異なるテンプレートとコントローラーをロードする単一のルートです

私は正しい線に沿って考えていますか、または別のソリューションを実装する必要がありますか?

これに関する助けは大歓迎です。

32
user3596298

ユーザーロールに基づいたテンプレートとコントローラーの読み込み

技術的にはui-router templateUrl関数はサービスのインジェクトをサポートしていませんが、templateProviderを使用してservice変数を保持するか非同期でロードし、$templateFactoryを使用してroleをインジェクトでき​​ます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を挿入できます。これは、roleで解決済みのtemplateProviderをすでに持っています。 ui-router状態定義内のcontrollerProviderオプションの次の例を見てください。

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

もちろん、このコードから重複を簡単に削除し、よりアクセスしやすいマイクロDSLを定義して、特定の役割とビューに異なるルールを簡単に定義できます。

次の demo は、コードを理解するのに役立ちます。

これは正しいアプローチですか?

通常、これはコンテキストに大きく依存します。 yoを支援するために、最初に以下の質問を提案させてください。

  • 役割に提示されるviewsはどれくらい異なりますか?

いくつかのbuttonsとその他のアクション要素のみを非表示にして、基本的にページを通常のユーザーのみが読み取り、スーパーユーザーが編集できるようにしますか?変更が小さい場合は、おそらく同じビューを使用して特定の要素のみを非表示にし、特定の機能を宣言的に有効/無効にするng-ifに似たディレクティブを偽造する可能性がありますonly-role='operator, admin'。一方、viewsが大幅に異なる場合、異なるテンプレートを使用するとマークアップが大幅に簡素化されます。

  • 特定のpageで利用可能なactionsは、役割によって異なりますか?

表面上で似ているように見えるアクションは、役割ごとに内部の仕組みが異なりますか?例では、Editアクションがuseradminの両方のロールで使用可能ですが、ある場合にはで開始します/ wizardUIやその他の上級ユーザー向けの複雑な形式の場合は、個別のcontrollerを使用する方が理にかなっています。一方、adminアクションがuserアクションのスーパーセットである場合、単一のコントローラーを使用する方がわかりやすいようです。どちらの場合もcontrollerを維持することは効果があることに注意してください。サービス/ビューモデル/モデル/名前の選択にカプセル化された動作にビューを接着するだけです

  • アプリのさまざまな場所から特定のpageにつながる多くのコンテキスト的に分離されたlinksがありますか?

たとえば、現在のユーザーに関係なくui-sref="dashboard"と書くだけで特定のpageへのナビゲーションを提供できるroleは、さまざまなコンテキスト。その場合、それらを単一のルート/状態で定義すると、ロールに基づいて異なるui-sref/ng-hrefを構築するために使用される条件付きロジックの方が保守しやすいように見えます。ただし、ユーザーロールに基づいてルート/状態を動的に定義する-動的にロードするかどうか

  • 特定のpageの異なるロールで使用可能なビューとアクションは、個別にまたは一緒に進化しますか?

最初に通常のユーザー向けに機能を構築し、次にプレミアム向け機能を構築し、次に究極の機能を構築する場合があります。特に明確な境界線を簡単に描くことができる場合、チームメンバー間でuseradminのページで作業を分けることは珍しいことではありません。このような場合、viewscontrollersを別々に用意することで、開発者は競合を避けて作業することができます。もちろん、すべての虹とユニコーンではありません-チームは重複を取り除くために非常に規律を守らなければなりませんそれは起こりそうです。

私の提案があなたの決定に役立つことを願っています。

23
miensol

私は正しい線に沿って考えていますか、または別のソリューションを実装する必要がありますか?

IMO、このようにすべきではありません。

ここでは、アプリケーションの実装方法に応じて、他に2つのソリューションを提案します。

1)ロールの権限設定可能の場合(ロールを設定する別のページがあり、ロールに権限を割り当てます...)。次に、ロール(通常のユーザー、管理ユーザーなど)に1つのテンプレートと1つのコントローラーのみを使用し、ng-showng-class、..それに応じてHTMLを表示します。

この場合、ユーザーが通常のユーザーであるか管理者ユーザーであるかはあまり気にしません。これは単にロールの名前です。私たちが気にしているのはrightsで、動的です=>したがって、設定された権限に基づいて動的にhtmlを表示する必要があります(確かに、ユーザーがユーザーが悪意のあるHTTPリクエストを作成してサーバーに投稿するのを防ぎます)。そのために別のテンプレートを使用する場合、countlessのケースがあります。

このソリューションのポイントは、ページの機能がロールに対して同じであり、ユーザーに基づいてページの機能をshow/hideするだけでよいことです。

2)ロールの権限固定(設定不可)および機能通常のユーザーと管理ユーザーのビューが異なる場合。これらのビューにseparate状態を使用し、ログインしているユーザーに基づいてこれらのビューへのアクセスを承認することをお勧めします(確かに、ユーザーがアクションを実行するときにサーバー側にも承認があります)。

理由は次のとおりです。管理者ユーザービューと通常のユーザービューには異なる機能(互いに分離する必要があります)

15
Khanh TO

バージョンangular 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';
            }
        }
      };
    }]);
})(); 
7
Shawn C.

I. Donotuse"...異なるテンプレートをロードする単一のルート..."は、私の提案でしょう。私の答え。

可能なら:

後戻りして全体のデザインを再検討し、
アプリケーションusersがurlに関心があるというweaken senseを試してください。

ではない。そして、urlアドレスバー...を本当に理解している場合は、copysend、およびpasteに使用します...その部分を調査しません...

II.提案: i-routerの使用を強制states

... UI-Routeraround statesで構成され、[〜#〜] optionally [〜#〜]にルートやその他の動作が付加される場合があります。 。

つまり、明確に定義された状態のグループ/階層としてアプリケーションを再考しましょう。 urlを定義できますが、ではなく、/(例: エラー状態 URLなし)

III.州周辺でアプリケーションを構築することでどのように利益を得ることができますか?

懸念の分離-私たちの目的であるべきです。

stateは、いくつかのビュー/コントローラーリゾルバーカスタムデータを収集するユニットです。 ..

つまり、より多くのstatesが再利用できることを意味しますviewscontrollersなど。そのようなstatesは本当に異なる(同じビュー、異なるコントローラー)。しかし、それらは単一の目的です-それらはいくつかのシナリオを処理するためにあります:

  • ユーザー/従業員レコードの管理
  • ユーザー/従業員のリスト-PhoneList情報(メール、電話など)
  • セキュリティ管理-ユーザーの権利とは...

また、多くのstatesが存在する可能性があります。州が100であっても、パフォーマンスの問題にはなりません。これらは単なる定義であり、他の部分への参照のセットであり、本当に必要な場合は後で使用する必要があります。

ユースケースユーザーストーリーstateのレベルで定義したら、それらをセット/階層にグループ化できます。
これらのグループは、後で異なる形式(異なるメニュー項目)で異なるユーザーロールに表示できます。

しかし、最終的には、多くの自由が得られ、保守性が簡素化されました。

IV.アプリケーションを実行し続けるおよび成長中

状態がほとんどない場合、maintainanaceは問題ではないようです。しかし、アプリケーションが成功する可能性があります。成功し、成長します...そのデザインの内側。

状態定義(作業単位として)とその階層(どのユーザーロールがどの状態にアクセスできるか)を分割すると、管理が簡単になります。

状態の外側にセキュリティを適用する(イベントリスナーala '$stateChangeStart'の方がはるかに簡単で、テンプレートプロバイダーのリファクタリングが終了することはありません。また、セキュリティの主要部分は、UIが許可するものに関係なく、サーバーに適用する必要があります

V.要約:

templateProviderのような素晴らしい機能がありますが、それは私たちのためにいくつかの興味深いことをすることができます(例: AngularJsでUI-Routerを使用してナビゲーションメニューを変更する )...

...セキュリティのために使用しないでください。これは、現在の役割に基づいて、既存の状態から構築されたメニュー/階層として実装できます。イベントリスナーは、ユーザーが許可された状態になっているかどうかを確認する必要がありますが、メインチェックはサーバーに適用する必要があります...

4
Radim Köhler

あなたは本当にルーターでそれを行う必要はありません。

最も簡単なことは、すべてのロールに対して1つのテンプレートを使用し、その中に動的なng-includeを使用することです。 $ scopeにインジェクターがあるとします:

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

したがって、user_dashboard.htmlビューとadmin_dashboard.htmlビューが必要です。それぞれの内部に、user_dashboard.htmlなどの個別のコントローラーを適用できます。

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

私は次のソリューションを採用しました(これは理想的ではないかもしれませんが、そのようなシナリオではうまくいきました)。

  1. ngControllerを使用して、テンプレート自体にコントローラーを指定します。

  2. 汎用ビュー名(_views/dashboard.html_など)を使用してテンプレートをロードします。

  3. ログインしているユーザーロールが変更されるたびに、$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'
        }).
        ...
});
_

こちらもご覧くださいショートデモ
(簡単にするためにngRouteを使用していますが、すべての作業はUserサービスによって行われるため、anyの違いはありません。)

3
gkalpak

この質問が投稿されてからしばらく経ちましたが、使用する方法が他の回答とは異なるため、回答を追加しています。

この方法では、ユーザーのロールに基づいてルートとテンプレートの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;
                 }
     }

});

上記に加えて、ユーザーにデータを表示する前に、サーバー側の認証チェックを行う必要があります。

1
Neel

ここで長い説明は必要ありません。

解決して$ 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();
      }
    });
});
1
Gepsens

素晴らしいプロジェクトがあります https://github.com/Narzerus/angular-permission ui-routerが必要です。それにもかかわらず、このプロジェクトはうまく機能しています。

0
TJ_