2014-02-23 1 views
12

Ich habe einen AngularJS Dienst, einzelne Kontakte abzurufen verwendet wird (/contacts/:id), basierend auf einem Index (/contacts):inkrementeller UI Updates von mehreren Versprechen

app.service("CollectionService", function($http, $q) { 
    this.fetch = function(collectionURL) { 
     return $http.get(collectionURL).then(function(response) { 
      var urls = response.data; 
      var entities = urls.map(function(url) { 
       return $http.get(url); 
      }); 
      return $q.all(entities); 
     }).then(function(responses) { 
      return responses.map(function(response) { 
       return response.data; 
      }); 
     }); 
    }; 
}); 

// used within the controller: 
CollectionService.fetch("/contacts").then(function(contacts) { 
    $scope.contacts = contacts; 
}); 

Die Ergebnisse in einer einfachen Liste angezeigt (<li ng-repeat="contact in contacts">{{ contact }}</li>).

Aufgrund der Verwendung von $q.all wird diese Liste jedoch erst aktualisiert, wenn die letzte (langsamste) Antwort empfangen wurde. Wie würde man von diesem Massenupdate zu inkrementellen Updates wechseln, wenn einzelne Kontakte empfangen werden?

Antwort

5

Sie könnten Make geladen Ihr eigenes Versprechen verwenden, um zurückzukehren und dann in Haken eine Benachrichtigung über das Versprechen, Ihnen eine Aktualisierung des Ladefortschritts zu geben und immer noch $q.all zu verwenden, um festzustellen, wann dies abgeschlossen ist. Es ist im Grunde, was Sie jetzt mit einer etwas anderen Art der Handhabung und Verwendung eines benutzerdefinierten Versprechens haben.

Fiddle: http://jsfiddle.net/U4XPU/1/

HTML

<div class="wrapper" ng-app="stackExample"> 
    <div class="loading" ng-show="loading">Loading</div> 
    <div class="contacts" ng-controller="ContactController"> 
     <div class="contact" ng-repeat="contact in contacts"> {{contact.name}} - {{contact.dob}}</div> 
    </div> 
</div> 

-Controller

.controller("ContactController", ["$scope", "CollectionService", function (scope, service) { 
    scope.contacts = []; 
    scope.loading = true; 

    service.fetch("/contacts") 
     .then(
    // All complete handler 
    function() { 
     console.log("Loaded all contacts"); 
     scope.loading = false; 
    }, 
    // Error handler 
    function() { 
     scope.error = "Ruh roh"; 
     scope.loading = false; 
    }, 
    // Incremental handler with .notify 
    function (newContacts) { 
     console.log("New contacts found"); 
     scope.contacts = scope.contacts.concat(newContacts); 
    }); 
}]) 

Dienst

.service("CollectionService", ["$q", "$http", function (q, http) { 

    this.fetch = function (collectionUrl) { 

     var deferred = q.defer(); 

     http.get(collectionUrl) 
     .then(function (response) { 

      // Still map all your responses to an array of requests 
      var allRequests = response.data.map(function (url) { 
       return http.get(url) 
        .then(function (innerResponse) { 
         deferred.notify(innerResponse.data); 
        }); 
      }); 

      // I haven't here, but you could still pass all of your data to resolve(). 
      q.all(allRequests).then(function() { 
       deferred.resolve(); 
      }); 

     }); 

     return deferred.promise; 

    } 

}]); 

können Sie behandeln auch die Fehler, wie Sie fi sehen t und .reject() das Versprechen:

http://docs.angularjs.org/api/ng/service/$q

+1

Es scheint so, als ob 'notify' genau das ist, nach dem ich gesucht habe - nicht sicher, wie ich das vorher übersehen hatte. Vielen Dank! – AnC

1

Ich habe mit einer Vermeidung des Problems kommen, ermöglicht eine onResponse Rückruf, die für jede Antwort einzeln aufgerufen wird:

var entities = urls.map(function(url) { 
    var request = $http.get(url); 
    if(onResponse) { 
     request.then(function(response) { 
      onResponse(response.data); 
     }); 
    } 
    return response; // this still allows for `$q.all` to handle completion 
}); 

Allerdings bin ich auf der API nicht daran interessiert, Rückrufe und Versprechungen Mischen - so bleibe ich neugierig, ob es eine elegantere Lösung gibt.

6

Sie könnten einfach die Kontaktliste in fetch() übergeben und die Liste auflisten lassen.

app.service("CollectionService", function($http, $q) { 
    this.fetch = function(collectionURL, resultList) { 
     $http.get(collectionURL).then(function(response) { 
      var urls = response.data; 
      urls.forEach(function(url) { 
       $http.get(url).then(function(response) { 
        resultList.push(response.data); 
       }); 
      }); 
     }; 
    }; 
}); 

// used within the controller: 
$scope.contacts = []; 
CollectionService.fetch("/contacts", $scope.contacts); 
+0

Ich wollte sehr viel Bewusstsein für die eventuelle Datenstruktur innerhalb des Dienstes zu vermeiden. Außerdem würde es keine Hinweise darauf geben, ob alle Artikel abgerufen wurden, ganz zu schweigen von der Fehlerbehandlung. – AnC

+0

Wenn Sie am Ende eine Benachrichtigung benötigen, können Sie jederzeit das $ q.all() hinzufügen. Dies würde es dem Benutzer dieser Funktion auch ermöglichen, die Daten in eine beliebige Datenstruktur umzuwandeln, nachdem alle URLs abgerufen wurden. Ich denke auch nicht, dass mit Ihrer Callback-Lösung etwas nicht in Ordnung ist. In diesem Artikel (http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/) heißt es: "Promises erlauben Entwicklern, ** 1x-only ** -Benachrichtigungen zu einer asynchronen Anfrage hinzuzufügen /Aktion.". Daher glaube ich nicht, dass es eine Möglichkeit gibt, mehrere Benachrichtigungen zu erhalten, ohne meine Lösung oder Rückrufe zu verwenden. – rob

0

Vielleicht könnte es funktionieren, wenn Sie nur eine leere Liste als Ergebnis zurück und lassen Sie die Service-Einträge auf jeder succceded Anfrage hinzufügen:

app.service("CollectionService", function($http, $q) { 
    this.fetch = function(collectionURL) { 
     var list = []; 
     $http.get(collectionURL).then(function(response) { 
      var urls = response.data; 
      urls.forEach(function(url) { 
       $http.get(url).then(function(response) { 
        list.push(response.data); 
       }); 
      }); 
     }; 
     return list; 
    }; 
}); 

$scope.contacts = CollectionService.fetch("/contacts"); 
+0

Ich hatte das in Erwägung gezogen, aber das schien mir eher ein Workaround zu sein als mein Callback-basierter. (Zum einen gibt es keinen Hinweis darauf, ob alle Artikel abgerufen wurden, ganz zu schweigen von der Fehlerbehandlung.) – AnC

0

Wenn ich richtig verstehe, Sie benötigen eine Lösung, die sollte

  • beginnen zeigt Kontakte vor allen Kontakten geladen
  • Rückrufe vermeiden
  • wissen, wenn alle Kontakte
  • sicher nur Service kennt Datenstruktur
  • zulassen Handhabungsfehler in der Steuerung

app.service("CollectionService", function($http, $q) { 
    this.fetch = function(collectionURL) { 
     return $http.get(collectionURL).then(function(response) { 
      var urls = response.data; 
      var contactPromises = urls.map(function(url) { 
       return $http.get(url).then(function(response) { 
        return $q.when(response.data); 
       }); 
      }); 
      return $q.when(contactPromises); 
     }); 
    }; 
}); 

// used within the controller: 
$scope.contacts = []; 
var addContact = function(contact) { $scope.contacts.push(contact); }; 

CollectionService.fetch("/contacts").then(function(contactPromises) { 
    contactPromises.forEach(function(contactPromise){ 
     contactPromise.then(addContact); 
    }); 
    return $q.all(contactPromise);   
}).then(function(){ 
    alert('All contacts loaded!'); 
}, function(){ 
    alert('Error!!'); 
});