Elaborazione della risposta $ http in servizio


233

Di recente ho pubblicato una descrizione dettagliata del problema che sto affrontando qui a SO. Non potendo inviare una $httprichiesta effettiva , ho usato il timeout per simulare il comportamento asincrono. L'associazione dei dati dal mio modello per visualizzare funziona correttamente, con l'aiuto di @Gloopy

Ora, quando uso $httpinvece di $timeout(testato localmente), vedo che la richiesta asincrona ha avuto esito positivo ed dataè piena di risposte json nel mio servizio. Ma il mio punto di vista non si aggiorna.

Plunkr aggiornato qui

Risposte:


419

Ecco un Plunk che fa quello che vuoi: http://plnkr.co/edit/TTlbSv?p=preview

L'idea è che lavori direttamente con le promesse e le loro funzioni "allora" per manipolare e accedere alle risposte restituite in modo asincrono.

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

Ecco una versione leggermente più complicata che memorizza nella cache la richiesta in modo da renderla la prima volta ( 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
C'è un modo per chiamare ancora i metodi di successo ed errore nel controller dopo l'intercettazione del servizio then?
Andyczerwonka,

2
@PeteBD Se desidero chiamare myService.async()più volte da vari controller, come organizzeresti il ​​servizio in modo tale da fare solo quello $http.get()per la prima richiesta, e tutte le richieste successive restituiscono solo un array di oggetti locale che viene impostato alla prima chiamata myService.async(). In altre parole, voglio evitare richieste multiple e inutili al servizio JSON, quando in realtà ho solo bisogno di fare una.
GFoley83,

5
@ GFoley83 - ecco qua: plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview . Se guardi la console vedrai che la richiesta viene effettuata una sola volta.
Pete BD,

3
@PeteBD Penso che tu possa anche usare $scope.data = myService.async()direttamente nel controller.
Julian,

2
@ Blowsie- Ho aggiornato i Plunks. Ecco l'originale (aggiornato alla 1.2RC3): plnkr.co/edit/3Nwxxk?p=preview Eccone uno che utilizza il servizio: plnkr.co/edit/a993Mn?p=preview
Pete BD

82

Lascia che sia semplice. È semplice come

  1. Ritorna promisenel tuo servizio (non è necessario utilizzarlo thennel servizio)
  2. Utilizzare thennel controller

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

Nel tuo link lo è app.factorye nel tuo codice lo è app.service. Si suppone app.factoryin questo caso.
Re Captcha,

1
funziona anche app.service. Inoltre, questa per me sembra la soluzione più elegante. Mi sto perdendo qualcosa?
user1679130

1
Sembra che ogni volta che ho un problema angolare, @allenhwkim ha la risposta! (3a volta questa settimana - ottimo componente ng-map tra l'altro)
Yarin,

voglio solo sapere come mettere successo ed errore qui con status_code
Anuj,

58

Poiché è asincrono, $scopesta ottenendo i dati prima che la chiamata ajax sia completa.

È possibile utilizzare $qnel servizio per creare promisee restituirlo al controller e il controller ottiene il risultato all'interno della then()chiamata contro promise.

Al vostro servizio,

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

Quindi, nel controller:

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

2
+1 Mi piace il migliore in quanto è più OO degli altri. Tuttavia c'è qualche motivo per cui non lo fai this.async = function() {e this.getData = function() {return data}? Spero tu capisca cosa intendo
bicicletta,

@bicycle L'ho voluto allo stesso modo ma non funzionerà perché la promessa deve essere risolta fino in fondo. Se non lo fai e provi ad accedervi come faresti normalmente, riceverai un errore di riferimento quando accedi ai dati interni. Spero abbia senso?
user6123723,

Se ho capito bene è necessario aggiungere deffered = $q.defer()all'interno di myService.async se voglio chiamare myService.async () due o più volte
demas

1
Questo esempio è un classico anti-pattern differito . Non è necessario presentare una promessa $q.deferpoiché il $httpservizio restituisce già una promessa. La promessa restituita si bloccherà se $httprestituisce un errore. Inoltre, i metodi .successe .errorsono obsoleti e sono stati rimossi da AngularJS 1.6 .
Georg

23

tosh shimayama ha una soluzione ma puoi semplificare molto se usi il fatto che $ http restituisce promesse e che promesse possono restituire un valore:

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

});

Una piccola dimostrazione in coffeescript: http://plunker.no.de/edit/ksnErx?live=preview

Il tuo plunker si è aggiornato con il mio metodo: http://plnkr.co/edit/mwSZGK?p=preview


Proverò ulteriormente lungo il tuo approccio. Ma, mi piace catturare il risultato in servizio invece di tornare. Vedi la domanda relativa a questo qui stackoverflow.com/questions/12504747/… . Mi piace elaborare i dati restituiti da $ http in diversi modi nel controller. Grazie ancora per il vostro aiuto.
bsr

puoi usare le promesse nei servizi, se non ti piace $ watch puoi fare ´promise.then (function (data) {service.data = data;}, onErrorCallback); `
Guillaume86,

Ho aggiunto un plunker biforcuto dal tuo
Guillaume86,

1
in alternativa puoi usare $ scope. $ emettere dal servizio e $ scope. $ sul ctrl per dire al controller che i dati sono tornati ma non vedo davvero un vantaggio
Guillaume86

7

Un modo molto migliore secondo me sarebbe qualcosa del genere:

Servizio:

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

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

        ...

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

        ...

        return deferred.promise;
    }

    return{
        getAllFruits:getAllFruits
    }

});

E nel controller puoi semplicemente usare:

$scope.fruits = FruitsManager.getAllFruits();

Angular inserirà automaticamente il risolto awesomeFruitsin $scope.fruits.


4
deferred.resolve ()? Sii più preciso per favore e dov'è la chiamata $ http? Inoltre, perché restituisci un oggetto in un servizio.

6

Ho avuto lo stesso problema, ma quando stavo navigando su Internet ho capito che $ http restituiva di default una promessa, quindi potevo usarlo con "then" dopo aver restituito i "dati". guarda il codice:

 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

Quando si associa l'interfaccia utente all'array, è necessario assicurarsi di aggiornare lo stesso array direttamente impostando la lunghezza su 0 e spingendo i dati nell'array.

Invece di questo (che imposta un diverso riferimento dell'array di datacui la tua UI non sarà a conoscenza):

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

prova questo:

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

Ecco un violino che mostra la differenza tra l'impostazione di un nuovo array e lo svuotamento e l'aggiunta a uno esistente. Non sono riuscito a far funzionare il tuo plnkr, ma spero che questo funzioni per te!


non ha funzionato. nel registro della console, ho potuto vedere che d è stato aggiornato correttamente nel callback di successo, ma non nei dati. Potrebbe essere la funzione già eseguita.
bsr

Questo metodo dovrebbe sicuramente funzionare forse ha qualcosa a che fare con il tipo di dati di d non essere un array (ad esempio in asp.net dovresti accedere a dd per l'array). Vedi questo plnkr per un esempio che spinge una stringa nell'array in caso di errore: plnkr.co/edit/7FuwlN?p=preview
Gloopy

1
angular.copy(d, data)funzionerà anche. Quando viene fornita una destinazione al metodo copy (), eliminerà prima gli elementi della destinazione, quindi copierà quelli nuovi dall'origine.
Mark Rajcok,

4

A questo proposito ho riscontrato un problema simile, ma non con get o post realizzati da Angular ma con un'estensione creata da una terza parte (nel mio caso Chrome Extension).
Il problema che ho riscontrato è che l'estensione di Chrome non ritorna, then()quindi non sono stato in grado di farlo nel modo sopra indicato, ma il risultato è ancora asincrono.
Quindi la mia soluzione è quella di creare un servizio e procedere a un callback

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

Quindi nel mio controller

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

Spero che questo possa aiutare gli altri a ottenere lo stesso problema.


4

Ho letto http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/ [AngularJS ci permette di semplificare la nostra logica del controller ponendo una promessa direttamente sull'ambito, anziché consegnare manualmente il risolto valore in un callback di successo.]

così semplice e pratico :)

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

Spero che questo aiuto


non funziona il valore restituito di defrred.promisenon è una funzione.
Jürgen Paul,

@PineappleUndertheSea perché deve essere una funzione? È un oggetto promettente.
Chev,

@PineappleUndertheSea intendevi usare differito e non scongelato?
Derrick,

2
Come PeteBD sottolineato, questa forma $scope.items = Data.getData(); è deprecato in Anglular
più elegante

2

Non mi piace davvero il fatto che, a causa del modo "promettente" di fare le cose, il consumatore del servizio che utilizza $ http deve "sapere" su come decomprimere la risposta.

Voglio solo chiamare qualcosa e ottenere i dati, in modo simile al vecchio $scope.items = Data.getData();modo, che ora è deprecato .

Ho provato per un po 'e non ho trovato una soluzione perfetta, ma ecco il mio colpo migliore ( Plunker ). Potrebbe essere utile a qualcuno.

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

Quindi controller:

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

I difetti che posso già individuare sono

  • Devi passare l'oggetto a cui desideri aggiungere i dati , che non è un modello intuitivo o comune in Angolare
  • getDatapuò accettare il objparametro solo nella forma di un oggetto (anche se potrebbe anche accettare un array), il che non sarà un problema per molte applicazioni, ma è un limite irritato
  • Devi preparare l'oggetto di input $scope.datacon = {}per renderlo un oggetto (in sostanza quello che $scope.clearData()fa sopra), o = []per un array, o non funzionerà (stiamo già di dover assumere qualcosa su quali dati vengono). Ho provato a fare questo passo di preparazione IN getData, ma senza fortuna.

Tuttavia, fornisce un modello che rimuove la piastra di controllo "promettete da scartare" del controller e potrebbe essere utile nei casi in cui si desidera utilizzare determinati dati ottenuti da $ http in più di un posto mantenendolo ASCIUTTO.


1

Per quanto riguarda la memorizzazione nella cache della risposta in servizio, ecco un'altra versione che sembra più semplice di quella che ho visto finora:

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

    })();

});

questo servizio restituirà i dati memorizzati nella cache o $http.get;

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

0

Si prega di provare il codice qui sotto

È possibile dividere il controller (PageCtrl) e il servizio (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;
             });
         }
    }
}());

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.