サービスでの$ http応答の処理


233

最近私がここで直面している問題の詳細な説明をSOに投稿しました。実際の$httpリクエストを送信できなかったため、タイムアウトを使用して非同期動作をシミュレートしました。@Gloopyの助けを借りて、モデルからビューへのデータバインディングが正しく機能している

(ローカルでテストされた)$httpではなくを使用すると$timeout、非同期リクエストが成功dataし、サービスのjson応答で満たされていることがわかります。しかし、私の見解は更新されていません。

ここで Plunkrを更新しまし

回答:


419

ここにあなたが望むことをするPlunkがあります:http : //plnkr.co/edit/TTlbSv ?p=preview

この考え方は、Promiseとその「Then」関数を直接操作して、非同期で返された応答を操作およびアクセスするというものです。

app.factory('myService', function($http) {
  var myService = {
    async: function() {
      // $http returns a promise, which has a then function, which also returns a promise
      var promise = $http.get('test.json').then(function (response) {
        // The then function here is an opportunity to modify the response
        console.log(response);
        // The return value gets picked up by the then in the controller.
        return response.data;
      });
      // Return the promise to the controller
      return promise;
    }
  };
  return myService;
});

app.controller('MainCtrl', function( myService,$scope) {
  // Call the async method and then do stuff with what is returned inside our own then function
  myService.async().then(function(d) {
    $scope.data = d;
  });
});

リクエストをキャッシュする少し複雑なバージョンを次に示しますので、初回のみリクエストします(http://plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview):

app.factory('myService', function($http) {
  var promise;
  var myService = {
    async: function() {
      if ( !promise ) {
        // $http returns a promise, which has a then function, which also returns a promise
        promise = $http.get('test.json').then(function (response) {
          // The then function here is an opportunity to modify the response
          console.log(response);
          // The return value gets picked up by the then in the controller.
          return response.data;
        });
      }
      // Return the promise to the controller
      return promise;
    }
  };
  return myService;
});

app.controller('MainCtrl', function( myService,$scope) {
  $scope.clearData = function() {
    $scope.data = {};
  };
  $scope.getData = function() {
    // Call the async method and then do stuff with what is returned inside our own then function
    myService.async().then(function(d) {
      $scope.data = d;
    });
  };
});

13
サービスがインターセプトした後も、コントローラーの成功メソッドとエラーメソッドを呼び出す方法はありますthenか?
andyczerwonka 2013年

2
@PeteBD myService.async()さまざまなコントローラーから複数回呼び出す場合$http.get()は、最初の要求に対してのみを実行するようにサービスを編成し、その後のすべての要求がへの最初の呼び出しで設定されるローカルオブジェクト配列を返すだけmyService.async()です。つまり、実際に1つだけ作成する必要がある場合に、JSONサービスへの複数の不要な要求を回避したいのです。
GFoley83 2013年

5
@ GFoley83-どうぞ:plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview。コンソールを見ると、リクエストが1回だけ行われていることがわかります。
ピートBD

3
@PeteBD $scope.data = myService.async()コントローラで直接使用することもできると思います。
ジュリアン

2
@ Blowsie- Plunksを更新しました。これがオリジナルです(1.2RC3に更新):plnkr.co/edit/3Nwxxk?p=previewこれはサービスを使用しているものです:plnkr.co/edit/a993Mn?p
Pete BD

82

簡単にしましょう。それはと同じくらい簡単です

  1. promiseサービスに戻る(thenサービスで使用する必要はありません)
  2. thenコントローラーで使用する

デモ。http://plnkr.co/edit/cbdG5p?p=preview

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

app.factory('myService', function($http) {
  return {
    async: function() {
      return $http.get('test.json');  //1. this returns promise
    }
  };
});

app.controller('MainCtrl', function( myService,$scope) {
  myService.async().then(function(d) { //2. so you can use .then()
    $scope.data = d;
  });
});

あなたのリンクではそれはapp.factory、そしてあなたのコードではそれapp.serviceです。app.factoryこの場合を想定しています。
Re Captcha 2014

1
app.serviceも動作します。また、これは私にとって最もエレガントなソリューションのように見えます。何か不足していますか?
user1679130 2014

1
角度の問題が発生するたびに@allenhwkimが答えを持っているようです!(今週3回目
ヤリン

私はstatus_codeで成功とエラーをここに置く方法を知りたいだけです
Anuj

58

非同期であるため、は$scopeajax呼び出しが完了する前にデータを取得しています。

$qサービスで使用してpromise、それを作成してコントローラーに返すことができ、コントローラーthen()はに対する呼び出し内で結果を取得しpromiseます。

あなたのサービスでは、

app.factory('myService', function($http, $q) {
  var deffered = $q.defer();
  var data = [];  
  var myService = {};

  myService.async = function() {
    $http.get('test.json')
    .success(function (d) {
      data = d;
      console.log(d);
      deffered.resolve();
    });
    return deffered.promise;
  };
  myService.data = function() { return data; };

  return myService;
});

次に、コントローラーで:

app.controller('MainCtrl', function( myService,$scope) {
  myService.async().then(function() {
    $scope.data = myService.data();
  });
});

2
+1私はこれが他のものよりオブジェクト指向であるので、これが一番好きです。しかし、あなたがこれ行わない何らかの理由があるthis.async = function() {とはthis.getData = function() {return data}?私は、あなたは私が何を意味するかを取得願っています
自転車

@bicycle同じようにしたかったのですが、約束を完全に解決しなければならないので、うまくいきません。そうしないで通常どおりにアクセスしようとすると、内部データにアクセスするときに参照エラーが発生します。それが理にかなっていると思いますか?
user6123723 2013年

私が正しく理解していれば、追加する必要があるdeffered = $q.defer()2つの以上の時)私はmyService.asyncを(呼び出したい場合myService.async内
DEMAS

1
この例は、古典的な遅延アンチパターンです。サービスはすでにプロミスを返している$q.deferので、プロミスを作成する必要はありません$http。が$httpエラーを返した場合、返されたpromiseはハングします。さらに、.successおよび.errorメソッドは廃止され、AngularJS 1.6から削除されました
georgeawg 2016年

23

tosh shimayamaには解決策がありますが、$ httpがpromiseを返し、promiseが値を返すことができるという事実を利用すれば、かなり単純化できます。

app.factory('myService', function($http, $q) {
  myService.async = function() {
    return $http.get('test.json')
    .then(function (response) {
      var data = reponse.data;
      console.log(data);
      return data;
    });
  };

  return myService;
});

app.controller('MainCtrl', function( myService,$scope) {
  $scope.asyncData = myService.async();
  $scope.$watch('asyncData', function(asyncData) {
    if(angular.isDefined(asyncData)) {
      // Do something with the returned data, angular handle promises fine, you don't have to reassign the value to the scope if you just want to use it with angular directives
    }
  });

});

coffeescriptでの小さなデモ:http ://plunker.no.de/edit/ksnErx?live=preview

私の方法で更新されたあなたのプランカー:http ://plnkr.co/edit/mwSZGK?p=preview


私はあなたのアプローチに沿ってさらに試みます。しかし、私は戻るのではなく、サービスで結果をキャプチャするのが好きです。これに関連する質問をご覧ください。stackoverflow.com/ questions / 12504747 /…。$ httpから返されたデータをコントローラーでさまざまな方法で処理したい。ご協力いただきありがとうございます。
BSR

サービスでpromiseを使用できます。$ watchが気に入らない場合は、´promise.then(function(data){service.data = data;}、onErrorCallback); `
Guillaume86

私はあなたからフォークしたプランカーを追加しました
Guillaume86

1
または、サービスから$ scope。$ emitを使用し、ctrlで$ scope。$ onを使用して、データが返されたことをコントローラーに伝えることができますが、実際にはメリットはありません
Guillaume86

7

私が考えるはるかに良い方法は次のようなものです:

サービス:

app.service('FruitsManager',function($q){

    function getAllFruits(){
        var deferred = $q.defer();

        ...

        // somewhere here use: deferred.resolve(awesomeFruits);

        ...

        return deferred.promise;
    }

    return{
        getAllFruits:getAllFruits
    }

});

そしてコントローラーでは、あなたは単に使うことができます:

$scope.fruits = FruitsManager.getAllFruits();

Angularは自動的に解決済みawesomeFruitsをに入れ$scope.fruitsます。


4
deferred.resolve()?より正確にしてください。$ http呼び出しはどこにありますか?また、なぜ.serviceでオブジェクトを返すのですか?

6

私は同じ問題を抱えていましたが、インターネットでサーフィンをしているときに、$ httpがデフォルトでプロミスに戻ることを理解していたので、「データ」を返した後に「それから」でそれを使用できました。コードを見てください:

 app.service('myService', function($http) {
       this.getData = function(){
         var myResponseData = $http.get('test.json').then(function (response) {
            console.log(response);.
            return response.data;
          });
         return myResponseData;

       }
});    
 app.controller('MainCtrl', function( myService, $scope) {
      // Call the getData and set the response "data" in your scope.  
      myService.getData.then(function(myReponseData) {
        $scope.data = myReponseData;
      });
 });

4

UIを配列にバインドするときは、長さを0に設定してデータを配列にプッシュすることにより、同じ配列を直接更新する必要があります。

これの代わりに(dataUIが認識しない別の配列参照を設定します):

 myService.async = function() {
    $http.get('test.json')
    .success(function (d) {
      data = d;
    });
  };

これを試して:

 myService.async = function() {
    $http.get('test.json')
    .success(function (d) {
      data.length = 0;
      for(var i = 0; i < d.length; i++){
        data.push(d[i]);
      }
    });
  };

新しい配列を設定することと空にすることと、既存の配列に追加することの違いを示すフィドルがあります。私はあなたのplnkrを動作させることができませんでしたが、うまくいけばこれはあなたのためにうまくいきます!


それはうまくいきませんでした。コンソールログで、dが成功コールバックで正しく更新されているのを確認できましたが、データは更新されていません。関数がすでに実行されている可能性があります。
BSR

この方法は確実に機能するはずです。おそらく、データ型dが配列ではないことに関係しています(asp.netでは、配列のddにアクセスする必要があります)。エラー時に文字列を配列にプッシュする例については、このplnkrを参照してください:plnkr.co/edit/7FuwlN
p=

1
angular.copy(d, data)も動作します。宛先がcopy()メソッドに提供されると、宛先の要素が最初に削除され、次にソースから新しい要素がコピーされます。
Mark Rajcok 2013

4

これに関連して、私は同様の問題を経験しましたが、Angularによって作成されたgetまたはpostではなく、サードパーティによって作成された拡張機能(私の場合はChrome拡張機能)を使用しました。
私が直面した問題は、Chrome拡張機能が返さthen()れないため、上記のソリューションではそれを行うことができませんでしたが、結果は非同期のままです。
だから私の解決策は、サービスを作成してコールバックに進むことです

app.service('cookieInfoService', function() {
    this.getInfo = function(callback) {
        var model = {};
        chrome.cookies.get({url:serverUrl, name:'userId'}, function (response) {
            model.response= response;
            callback(model);
        });
    };
});

それから私のコントローラーで

app.controller("MyCtrl", function ($scope, cookieInfoService) {
    cookieInfoService.getInfo(function (info) {
        console.log(info);
    });
});

これが他の人が同じ問題を解決するのに役立つことを願っています。


4

http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/を読みました [AngularJSを使用すると、手動で解決策を渡すのではなく、スコープに直接プロミスを配置することでコントローラーロジックを合理化できます成功コールバックの値。]

とても簡単で便利です:)

var app = angular.module('myApp', []);
            app.factory('Data', function($http,$q) {
                return {
                    getData : function(){
                        var deferred = $q.defer();
                        var promise = $http.get('./largeLoad').success(function (response) {
                            deferred.resolve(response);
                        });
                        // Return the promise to the controller
                        return deferred.promise; 
                    }
                }
            });
            app.controller('FetchCtrl',function($scope,Data){
                $scope.items = Data.getData();
            });

この助けを願っています


動作しません。の戻り値はdefrred.promise関数ではありません。
ユルゲンポール

@PineappleUndertheSeaなぜ関数である必要があるのですか?それはpromiseオブジェクトです。
Chev

@PineappleUndertheSeaあなたはdeferredではなくdeferredを使用するつもりでしたか?
デリック

2
PeteBDが指摘したように、このフォーム$scope.items = Data.getData(); はAnglularでは推奨されていません
最高の

2

「約束」の方法のため、$ httpを使用するサービスのコンシューマーは、応答をアンパックする方法を「知っている」必要があるという事実が本当に好きではありません。

古い$scope.items = Data.getData();方法と同様に、何かを呼び出してデータを取得したいだけですが、現在は非推奨です

しばらく試しましたが、完璧な解決策は思い付きませんでしたが、これが私のベストショットです(Plunker)。誰かに役立つかもしれません。

app.factory('myService', function($http) {
  var _data;  // cache data rather than promise
  var myService = {};

  myService.getData = function(obj) { 
    if(!_data) {
      $http.get('test.json').then(function(result){
        _data = result.data;
        console.log(_data);  // prove that it executes once
        angular.extend(obj, _data);
      }); 
    } else {  
      angular.extend(obj, _data);
    }
  };

  return myService;
}); 

次にコントローラー:

app.controller('MainCtrl', function( myService,$scope) {
  $scope.clearData = function() {
    $scope.data = Object.create(null);
  };
  $scope.getData = function() {
    $scope.clearData();  // also important: need to prepare input to getData as an object
    myService.getData($scope.data); // **important bit** pass in object you want to augment
  };
});

私がすでに見つけることができる欠陥は

  • データを追加するオブジェクトを渡す必要があります。これは、Angularの直感的または一般的なパターンではありません。
  • getDataobjオブジェクトの形式でのみパラメーターを受け入れることができます(ただし、配列を受け入れることもできます)。これは、多くのアプリケーションでは問題にはなりませんが、制限があります。
  • 入力オブジェクトを準備$scope.data= {}て、それをオブジェクト(基本的に$scope.clearData()上記で何が行われるか)にするか= []、配列にする必要があります。そうしないと機能しません(データが何であるかについてはすでに想定している必要があります)。私はこの準備ステップをINにしようとしましたgetDataが、うまくいきませんでした。

それにもかかわらず、これはコントローラーの「promise unwrap」ボイラープレートを削除するパターンを提供し、DRYを維持しながら$ httpから取得した特定のデータを複数の場所で使用したい場合に役立つことがあります。


1

サービスでの応答のキャッシュに関する限り、これまでに見たものよりも簡単に見える別のバージョンを次に示します。

App.factory('dataStorage', function($http) {
     var dataStorage;//storage for cache

     return (function() {
         // if dataStorage exists returned cached version
        return dataStorage = dataStorage || $http({
      url: 'your.json',
      method: 'GET',
      cache: true
    }).then(function (response) {

              console.log('if storage don\'t exist : ' + response);

              return response;
            });

    })();

});

このサービスは、キャッシュされたデータまたはを返します$http.get

 dataStorage.then(function(data) {
     $scope.data = data;
 },function(e){
    console.log('err: ' + e);
 });

0

以下のコードをお試しください

コントローラ(PageCtrl)とサービス(dataService)を分割できます

'use strict';
(function () {
    angular.module('myApp')
        .controller('pageContl', ['$scope', 'dataService', PageContl])
        .service('dataService', ['$q', '$http', DataService]);
    function DataService($q, $http){
        this.$q = $q;
        this.$http = $http;
        //... blob blob 
    }
    DataService.prototype = {
        getSearchData: function () {
            var deferred = this.$q.defer(); //initiating promise
            this.$http({
                method: 'POST',//GET
                url: 'test.json',
                headers: { 'Content-Type': 'application/json' }
            }).then(function(result) {
                deferred.resolve(result.data);
            },function (error) {
                deferred.reject(error);
            });
            return deferred.promise;
        },
        getABCDATA: function () {

        }
    };
    function PageContl($scope, dataService) {
        this.$scope = $scope;
        this.dataService = dataService; //injecting service Dependency in ctrl
        this.pageData = {}; //or [];
    }
    PageContl.prototype = {
         searchData: function () {
             var self = this; //we can't access 'this' of parent fn from callback or inner function, that's why assigning in temp variable
             this.dataService.getSearchData().then(function (data) {
                 self.searchData = data;
             });
         }
    }
}());

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