AngularJSの条件付きルーティングのバリアント

私はAngularJSを初めて使用します。最近、趣味のプロジェクトで使用することにしました。 かなり迅速に、特定の条件に従ってルーティングを設定するタスクに直面しました。そのような条件の最も単純で最も明白なのは、ユーザーが許可されているかどうかです。 アプリケーションには、すべてのユーザーに開かれているページと、ログインすることによってのみアクセスできるページが含まれています。 承認がない場合は、ユーザーに対して透過的に取得する必要があります。取得できない場合は、ログインページで修正してください。



私の知る限り、このタスクは非常に一般的ですが、すぐに使用できる簡単な既製の方法は見つかりませんでした。 グーグルでかなりの時間を費やし、ドキュメントを読んで実験して、私は自分のケースのためにかなりエレガントなソリューションを見つけることになりました。 急いで自転車を共有します。これにより、AngularJSの同じ初心者ユーザーの時間を節約できると思います。 おそらく、私が見つけられなかった何らかの理由で非常に標準的なソリューションを指す教祖がいるでしょう。 たとえば、私はまだui-routerを理解していません。



挑戦する



私の仕事についてもう少し書く価値があります。 単一ページのWebアプリケーション(単一ページアプリケーション)があります。 簡単にするため、パス「/」に沿って、つまりサイトのルートに、公開されている1つの「メイン」ページがあると想定します。 その上で、ユーザーは登録またはログインできます。 ログインに成功すると、アプリケーションは認証トークンとユーザープロファイルを受け取ります。 プロファイルはデータ構造を拡散するものであり、サーバーの負荷を軽減するために、各ページの一部ではなく、ログインで一度ロードしたいと思います。 認可トークンはブラウザのローカルストレージに長時間保存できますが、ページが更新されるたびにプロファイルデータが再度ダウンロードされます。 トークンを一度受け取った後、私はすべての閉じたページを自由に通過し、それらのいずれかをリロードし、ブックマークに追加することができます。 プロファイルは透過的にロードされます。 しかし、トークンが悪化した場合、またはサイトの閉じたページへのリンクを友人に提供した場合、そのサイトは私(または友人)をメインページに送信する必要があります。



ソリューションを検索する



Googleがリクエスト "Angularjs条件付きルーティング"で発行した唯一の有用なものは、stackoverflowに関するこの質問です。 そこで2つのソリューションが提案されました。



1つは、サーバーから401ステータスを送信し、$ httpサービスを介してインターセプトすることです。保護された各ページからサーバーへのリクエストが想定されます。 誰かに適しているかもしれませんが、私には適していません-データを一度ダウンロードしたら、クライアントでルーティングするためにサーバーを完成させたくありません。



2つ目は、メッセージ$ routeChangeStartをインターセプトし、送信先を確認し、許可があるかどうかを確認し、許可されていない場合はリダイレクトします。 または、$スコープを介してパスの変更をリッスンします$ Watch(function(){return $ location.path();}。このソリューションの欠点は次のとおりです。



1. $ routeChangeStartの場合、次のオブジェクトはルーティングパスを提供しません;目的地を理解することはあまり便利ではありません。 このメッセージは、存在しないページからのリダイレクトでもスローされます。その結果、ルーティングの条件の表現はあまり美しくなく、テンプレートの名前、コントローラーの名前、その他の奇妙なものに結び付けられます。

2.私の場合のように、データをロードする必要がある場合、ダウンロードが終了するまでルーティングを遅らせる方法はありません。 この場合、ルーティングはユーザープロファイル自体のデータに依存する場合があります。たとえば、新しいスーパーオファーがあり、すべてをドロップして、このオファーのページに緊急に移動する必要があります。



私は別の「ダウンロードページ」にリダイレクトする冗長データを考えていました。そこでデータをアップロードし、結果に応じてリダイレクトしますが、まず、ルーティングロジックは2つの場所に分散されます。 次に、ユーザーは履歴にこの中間ページを持ちます。 $ location.replace()を使用して履歴を消去できますが、何らかの理由でダウンロードが遅れ、ユーザーが[戻る]をクリックする時間があれば、間違ったページが消去されますが、別のページからも修正されるため、何らかの方法でこれを処理する必要がありますソリューションを単純化しないケース。 第三に、「第二」からの状況を考慮して、正しく修正するためにどこに行ったかを覚えておく必要があります。 この決定は私に刺激を与えませんでした。



解決策



AngularJSは、興味深い名前$ qのサービスを提供します。 qおよび遅延/約束の仕様が非常にシンプルで興味深い概念である理由をドキュメントで読むことができます。 要するに、私たちはサービスに特別な施設を作るよう頼みます



var defered = $q.defer();









このオブジェクトからpromiseオブジェクトを取得し、クライアントにコードを提供します



return defered.promise;









操作の成功と失敗のプロミスコールバックでクライアントがハングする



promise.then(function (result) {...}, function (reason) {...});









今、私たちが私たちの施設で行うとき



defered.resolve(result);









または



defered.reject();









クライアントは適切なコールバックと呼ばれます。 通常のコールバックよりも優れているものは何ですか? promiseはチェーンに結合できます(詳細についてはドキュメントを参照)。これは私のタスクにとって重要であり、多くのAngularJSサービスはこれらと連携できます。たとえば、$ routerProviderでルート構成の解決フィールドを指定し、promiseを返す関数を渡すことができます。 さらに、この関数がプロミスではないオブジェクトを返す場合、それはすでに解決されたプロミスとして解釈されます。 ルートはプロミスが解決されるまで待機し、拒否が発生した場合は完全にキャンセルされます。 その後、すべてが簡単です-データをロードする関数を作成し、必要に応じてすべてのチェックとリダイレクトを行います。 データをロードする必要がある場合、promiseが返されます。リダイレクトを行う必要がある場合、古いルートが無駄に待たないように、promiseはその前にリダイレクトされます。



ソリューションコード:



 '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 defered = $q.defer(); $http.post("/loadUserProfile", { userToken: "blah" }) .success(function (response) { $rootScope.userProfile = response.userProfile; defered.resolve(true); }) .error(function () { defered.reject(); $location.path("/"); }); return defered.promise; } };
      
      







その結果、非常にシンプルかつ透過的に判明しました。ネットワーク上ですぐにこれを見つけられなかったのは不思議なことです(今では見つけやすくなることを願っています)。 欠点の中で、各ルートで解決を指定する必要がありますが、一方で、構成の明確さと柔軟性を提供します-いくつかの同じチェック*関数(異なるページのロジックが完全に異なる場合)をさらに記述し、必要に応じて使用できます。



更新:コメントは、$ http.post()からチェーンでプロミスを送信するコードを書くことを奨励しました。これは、$ httpサービスの他のメソッドと同様に、プロミスを返します。 約束のこの種の自然な使用により、明確な契約でプロセスを段階と機能にクールで明確に分割できます。



promiseチェーンのメカニズムは次のとおりです。thenpromiseメソッドは別の「派生」promiseを返します。これは、thenで指定されたハンドラーのいずれかが返す値(リゾルバーまたは正規表現)で解決されるか、ハンドラーの1つが例外をスローした場合に検出されます。 さらに、戻り値がプロミス自体である場合、その結果は派生プロミスの結果を決定します。 したがって、デリバティブプロミスを登録するには、$ q.reject()を返すだけで十分です。



その結果、ソリューションは次のようになります。



 //    ,   resolve . var checkRouting = function ($q, $rootScope, $http, $location, localStorageService) { //     function redirect(path) { if ($location.path() != path) { $location.path(path); //  . return $q.reject(); //   . } else { return true; //   . } } return getUserDataPromise($q, $rootScope, $http, localStorageService) .then(function (userData) { //       . if (userData.sales.lenght > 0) { return redirect("/sales"); //   ,   ! } else { return true; //  ,  . } }, function (reason) { //      . console.error(reason); //     ; ) return redirect("/"); }); }; //  ,      userData,  . var getUserDataPromise = function ($q, $rootScope, $http, localStorageService) { if ($rootScope.userData) { return $q.when($rootScope.userData); //    ( ) . } else { var userToken = localStorageService.get("userToken"); if (!userToken) { return $q.reject("No user token."); //     . } else { //  ,        , //  ,     . return $http.post("/loadUserData", { userToken: userToken }) .then(function (result) { if (result.data.userData) { $rootScope.userData = result.data.userData; return result.data.userData; } else { return $q.reject("Got response from server but without user data."); } }, function (reason) { return $q.reject("Error requesting server: " + reason); }); } } };
      
      






All Articles