AngularJS:HTTPインターセプターへのサービスの注入(循環依存)


118

AngularJSアプリが認証を処理するためのHTTPインターセプターを作成しようとしています。

このコードは機能しますが、Angularがこれを自動的に処理することになっていると思ったので、サービスを手動で注入することについて心配しています。

    app.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function ($location, $injector) {
        return {
            'request': function (config) {
                //injected manually to get around circular dependency problem.
                var AuthService = $injector.get('AuthService');
                console.log(AuthService);
                console.log('in request interceptor');
                if (!AuthService.isAuthenticated() && $location.path != '/login') {
                    console.log('user is not logged in.');
                    $location.path('/login');
                }
                return config;
            }
        };
    })
}]);

私が始めたことは、循環依存の問題に遭遇しました:

    app.config(function ($provide, $httpProvider) {
    $provide.factory('HttpInterceptor', function ($q, $location, AuthService) {
        return {
            'request': function (config) {
                console.log('in request interceptor.');
                if (!AuthService.isAuthenticated() && $location.path != '/login') {
                    console.log('user is not logged in.');
                    $location.path('/login');
                }
                return config;
            }
        };
    });

    $httpProvider.interceptors.push('HttpInterceptor');
});

私が心配しているもう1つの理由は、Angular Docsの$ httpに関するセクションが、依存関係を「通常の方法」でHttpインターセプターに挿入する方法を示しているように見えることです。「インターセプター」の下のコードスニペットを参照してください。

// register the interceptor as a service
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
  return {
    // optional method
    'request': function(config) {
      // do something on success
      return config || $q.when(config);
    },

    // optional method
   'requestError': function(rejection) {
      // do something on error
      if (canRecover(rejection)) {
        return responseOrNewPromise
      }
      return $q.reject(rejection);
    },



    // optional method
    'response': function(response) {
      // do something on success
      return response || $q.when(response);
    },

    // optional method
   'responseError': function(rejection) {
      // do something on error
      if (canRecover(rejection)) {
        return responseOrNewPromise
      }
      return $q.reject(rejection);
    };
  }
});

$httpProvider.interceptors.push('myHttpInterceptor');

上記のコードはどこに行くべきですか?

私の質問は、これを行うための正しい方法は何ですか?

ありがとう、そして私の質問が十分に明確であることを望みます。


1
好奇心から、AuthServiceで使用している依存関係があれば教えてください。私はHTTPインターセプターのリクエストメソッドを使用して循環依存の問題を抱えていました。angularfireの$ firebaseAuthを使用しています。$ routeを使用するコードのブロックをインジェクターから削除すると(510行目)、すべてが機能し始めました。ここに問題がありますが、それはインターセプターで$ httpを使用することです。gitにオフ!
2014年

うーん、その価値のために、私の場合のAuthServiceは$ window、$ http、$ location、$ qに依存します
shaunlim

状況によってはインターセプターでリクエストを再試行するケースがあるので、への循環依存関係はさらに短くなり$httpます。私が見つけた唯一の回避策はを使用$injector.getすることですが、これを回避するためにコードを構造化する良い方法があるかどうかを知るのは素晴らしいことです。
Michal Charemza 2014年

1
@rewritten:github.com/angular/angular.js/issues/2367からの応答を見てください。これにより、同様の問題が修正されました。彼がやっていることは次のようなものです:$ http = $ http || $ injector.get( "$ http"); もちろん、$ httpを使用しようとしている独自のサービスに置き換えることができます。
ジョナサン

回答:


42

$ httpとAuthServiceの間に循環依存関係があります。

$injectorサービスを使用して何をしているのかは、AuthServiceに対する$ httpの依存を遅らせることで、鶏と卵の問題を解決しています。

あなたがしたことは、実際には最も簡単な方法だと思います。

これは次の方法でも実行できます。

  • 後でインターセプターを登録します(run()ブロックの代わりにブロックで登録すると、config()既にうまくいく場合があります)。しかし、$ httpがまだ呼び出されていないことを保証できますか?
  • 呼び出しAuthService.setHttp()などでインターセプターを登録しているときに$ httpをAuthServiceに手動で「挿入」します。
  • ...

15
この答えが問題をどのように解決しているか、私は知りませんでしたか?@shaunlim
Inanc

1
実際にはそれを解決するのではなく、アルゴリズムのフローがひどかったことを指摘するだけです。
ローマンM.コス2014

12
run()$ httpProviderをrunブロックに挿入できないため、ブロックにインターセプターを登録できません。これは、構成フェーズでのみ実行できます。
スティーブンフリードリヒ

2
循環参照の良い点ですが、それ以外の場合、それは受け入れられた回答ではありません。箇条書きのどちらも意味をなさない
ニコライ

65

これは私がやったことです

  .config(['$httpProvider', function ($httpProvider) {
        //enable cors
        $httpProvider.defaults.useXDomain = true;

        $httpProvider.interceptors.push(['$location', '$injector', '$q', function ($location, $injector, $q) {
            return {
                'request': function (config) {

                    //injected manually to get around circular dependency problem.
                    var AuthService = $injector.get('Auth');

                    if (!AuthService.isAuthenticated()) {
                        $location.path('/login');
                    } else {
                        //add session_id as a bearer token in header of all outgoing HTTP requests.
                        var currentUser = AuthService.getCurrentUser();
                        if (currentUser !== null) {
                            var sessionId = AuthService.getCurrentUser().sessionId;
                            if (sessionId) {
                                config.headers.Authorization = 'Bearer ' + sessionId;
                            }
                        }
                    }

                    //add headers
                    return config;
                },
                'responseError': function (rejection) {
                    if (rejection.status === 401) {

                        //injected manually to get around circular dependency problem.
                        var AuthService = $injector.get('Auth');

                        //if server returns 401 despite user being authenticated on app side, it means session timed out on server
                        if (AuthService.isAuthenticated()) {
                            AuthService.appLogOut();
                        }
                        $location.path('/login');
                        return $q.reject(rejection);
                    }
                }
            };
        }]);
    }]);

注:$injector.get呼び出しはインターセプターのメソッド内にある必要があります。呼び出しを他の場所で使用しようとすると、JSで循環依存エラーが発生し続けます。


4
手動注入($ injector.get( 'Auth'))を使用して問題を解決しました。よくできました!
ロバート

循環依存を回避するために、どのURLが呼び出されるかを確認しています。if(!config.url.includes( '/ oauth / v2 / token')&& config.url.includes( '/ api')){// OAuthサービスを呼び出す}。したがって、循環依存関係はもうありません。少なくとも私にとってはそれはうまくいった;)。
Brieuc

完璧です。これは、まさに私が同様の問題を修正するために必要なものです。ありがとう@shaunlim!
Martyn Chamberlin、

このサービスは匿名であり、テストを簡単に行うことができないため、このソリューションはあまり好きではありません。ランタイムに注入するはるかに良いソリューション。
kmanzana 2016年

それでうまくいきました。基本的に、$ httpを使用していたサービスを注入します。
Thomas

15

$ injectorを直接使用することはアンチパターンだと思います。

循環依存関係を解除する方法は、イベントを使用することです。$ stateを注入する代わりに、$ rootScopeを注入します。直接リダイレクトする代わりに、

this.$rootScope.$emit("unauthorized");

プラス

angular
    .module('foo')
    .run(function($rootScope, $state) {
        $rootScope.$on('unauthorized', () => {
            $state.transitionTo('login');
        });
    });

2
依存関係がないため、これはよりエレガントなソリューションだと思います。関連する場所ならどこでも、このイベントを聞くことができます
Basav

イベントをディスパッチした後に戻り値を取得できないため、これは私のニーズには機能しません。
xabitrigo 2017年

13

悪い論理はそのような結果をもたらしました

実際、ユーザーが作成したか、Http Interceptorにないかを検索するポイントはありません。すべてのHTTPリクエストを単一の.service(または.factory、または.provider)にラップし、それをすべてのリクエストに使用することをお勧めします。関数を呼び出すたびに、ユーザーがログインしているかどうかを確認できます。すべて問題なければ、送信リクエストを許可します。

あなたの場合、Angularアプリケーションはどんな場合でもリクエストを送信し、そこで承認をチェックするだけで、その後JavaScriptがリクエストを送信します。

あなたの問題の核心

myHttpInterceptor$httpProviderインスタンスの下で呼び出されます。あなたのAuthService用途$http、または$resource、そしてここに依存関係の再帰、または循環依存関係があります。からその依存関係を削除するとAuthService、そのエラーは表示されなくなります。


また、@ Pieter Herroelenが指摘したように、このインターセプターをモジュールに配置することもできますmodule.runが、これは解決策ではなく、ハックに似ています。

クリーンで自己記述的なコードを実行する場合は、いくつかのSOLID原則に従う必要があります。

少なくとも単一の責任の原則は、そのような状況で多くの助けになります。


5
私はこの答えの言葉がうまくいっているとは思いませんが、問題の根本に到達したと思います。現在のユーザーデータログインの手段(httpリクエスト)を格納する認証サービスの問題は、2つの原因があることです。代わりに、現在のユーザーデータを保存するための1つのサービスとログインのための別のサービスに分割されている場合、httpインターセプターは「現在のユーザーサービス」に依存するだけでよく、循環依存関係を作成する必要はありません。
Snixtor 2016年

@Snixtorありがとうございます!もっと明確にするために、もっと英語を学ぶ必要があります。
ローマンM.コス2016年

0

Auth状態(isAuthorized())のみをチェックしている場合は、その状態を別のモジュールに入れることをお勧めします。たとえば、「Auth」と言って、状態を保持し、$ http自体は使用しません。

app.config(['$httpProvider', function ($httpProvider) {
  $httpProvider.interceptors.push(function ($location, Auth) {
    return {
      'request': function (config) {
        if (!Auth.isAuthenticated() && $location.path != '/login') {
          console.log('user is not logged in.');
          $location.path('/login');
        }
        return config;
      }
    }
  })
}])

認証モジュール:

angular
  .module('app')
  .factory('Auth', Auth)

function Auth() {
  var $scope = {}
  $scope.sessionId = localStorage.getItem('sessionId')
  $scope.authorized = $scope.sessionId !== null
  //... other auth relevant data

  $scope.isAuthorized = function() {
    return $scope.authorized
  }

  return $scope
}

(私はlocalStorageを使用してここでクライアント側にセッションIDを格納しましたが、たとえば$ http呼び出しの後にこれをAuthService内に設定することもできます)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.