2013-08-09 6 views
7

Die Einstellung: Ich möchte einen Dienst haben, dass mehrere Controller Daten abfragen können, die mit $ http abgerufen werden. Die ursprüngliche Lösung bestand darin, Versprechungen wie vorgeschlagen here zu verwenden.

Das Problem: Jedes Mal, wenn ein Controller den Dienst abfragt, gibt der Dienst ein $ http-Versprechen zurück, was zu mehreren Abfragen führt, die dieselben Daten immer und immer wieder von einem Remote-Server abrufen.

Eine Lösung: Die Servicefunktion gibt entweder Daten oder ein Versprechen wie unten zurück. Und es ist Aufgabe des Controllers, dies zu überprüfen und entsprechend zu handeln. So

app.factory('myService', function($http) { 
    var items = []; 
    var myService = { 
     getItems: function() { 
      // if items has content, return items; otherwise, return promise. 
      if (items.length > 0) { 
       return items; 
      } else {  
       var promise = $http.get('test.json').then(function (response) { 
        // fill up items with result, so next query just returns items. 
        for(var i=0;i<response.data.length;i++){ 
         items.push(response.data[i]); 
        } 
        return items; 
       }); 
       // Return the promise to the controller 
       return promise; 
      } 
    }; 
    return myService; 
}); 

, wenn ein Controller benötigt, dass die Daten, die Steuerung funktioniert nur so etwas wie diese:

app.controller('MainCtrl', function(myService,$scope) { 
    var promiseOrData = myService.async(); 
    // Check whether result is a promise or data. 
    if (typeof promiseOrData.then === 'function'){ 
     // It's a promise. Use then(). 
     promiseOrData.then(function(data){ 
      $scope.data = data; 
     }); 
    } else { 
     // It's data. 
     $scope.data = data; 
    } 
}); 

Die Frage ist also: Gibt es einen besseren Weg, dies zu tun? Bei vielen Controllern hätte diese Methode viel doppelten Code. Idealerweise fragen die Controller den Dienst direkt nach Daten ab.

Danke!

Antwort

12

$ http gibt ein Versprechen zurück, wir können das verwenden, anstatt ein neues mit $ q zu erstellen. Sobald das Versprechen gelöst ist, können wir es immer wieder zurückgeben.

.factory('myService', ['$http','$q', function($http, $q) { 
    var items = []; 
    var last_request_failed = true; 
    var promise = undefined; 
    return { 
     getItems: function() { 
      if(!promise || last_request_failed) { 
       promise = $http.get('test.json').then(
       function(response) { 
        last_request_failed = false; 
        items = response.data; 
        return items; 
       },function(response) { // error 
        last_request_failed = true; 
        return $q.reject(response); 
       }); 
      } 
      return promise; 
     }, 
    }; 
}]) 

In Ihrem Controller:

myService.getItems().then( 
    function(data) { $scope.data = data; } 
); 
+0

Nice work. Noch besser reduziert es die Menge an Code in der Steuerung benötigt. –

+0

Irgendeine Möglichkeit, dies auf der Serviceseite DRY zu machen? Ich habe mehrere http-Endpunkte, an denen ich dies tun möchte und diesen Code für jeden Service/HTTP-Endpunkt zu wiederholen, würde außer Kontrolle geraten –

+0

@KyleKochis, das einzige, was ich tun konnte, ist einen einzigen [HTTP Interceptor] zu erstellen (http://StackOverflow.com/a/11957760/215945) HTTP-Fehler für alle meine Dienste abzufangen, so dass der obige Code keinen Fehlerhandler mehr benötigt. –

1

Erstellen Sie Ihr eigenes Versprechen, das entweder die zwischengespeicherten Daten oder die abgerufenen Daten löst.

app.factory('myService', function($http, $q) { 
    var items = []; 
    var myService = { 
     getItems: function() { 
      var deferred = $q.defer(); 

      if (items.length > 0) { 
       //resolve the promise with the cached items 
       deferred.resolve(items); 
      } else {  
       $http.get('test.json').then(function (response) { 
        // fill up items with result, so next query just returns items. 
        for(var i=0;i<response.data.length;i++){ 
         items.push(response.data[i]); 
        } 
        //resolve the promise with the items retrieved 
        deferred.resolve(items); 
       },function(response){ 
        //something went wrong reject the promise with a message 
        deferred.reject("Could not retrieve data!"); 
       }); 


      } 
     // Return the promise to the controller 
     return deferred.promise; 
    }; 
    return myService; 
}); 

Dann verbrauchen das Versprechen in Ihrem Controller.

app.controller('MainCtrl', function(myService,$scope) { 
    var promiseOrData = myService.getItems(); 

     promiseOrData.then(function(data){ 
      $scope.data = data; 
     }, 
     function(data){ 
      // should log "Could not retrieve data!" 
      console.log(data) 
     }); 

});