50

I have a list that looks like this:

<li ng-repeat="document in DisplayDocuments()" ng-class="IsFiltered(document.Filtered)">
    <span><input type="checkbox" name="docChecked" id="doc_{{document.Id}}" ng-model="document.Filtered" /></span>
    <span>{{document.Name}}</span>
</li>

I bind this list in my controller, to this:

$scope.Documents = $http.get('/Documents/DocumentsList/' + caseId).then(function(result) {
    return result.data;
});

When this runs, I dont get any results. when I remove the then method, I get three empty lines, making the count OK, but no information is displayed.

I know "everthing" else works, since I previously populated the list with jQuery, what am I doing wrong?

Here's the response from the server:

{Id:3f597acf-a026-45c5-8508-bc2383bc8c12, Name:ZZ_BL0164_Skisse BL0164_945111.pdf, Order:1,…}
{Id:46f51f1f-02eb-449a-9824-8633e8ae7f31, Name:ZB_BL0201_Firmaattest BL0201_945111.pdf, Order:1,…}
{Id:fddd1979-c917-4b32-9b83-b315f66984ed, Name:ZA_BL0228_Legitimasjonsskjema BL0228_945111.pdf,…}
Damjan Pavlica
  • 31,277
  • 10
  • 71
  • 76
ruffen
  • 1,695
  • 2
  • 25
  • 51
  • I normally use .get(...).success(function (data) {}); maybe you can add console.log(result) and see what it outputs? I'd also make sure that the url you call is well formed. what is caseId? – Eduard Gamonal May 31 '13 at 11:08
  • 1
    I have tried that as well, just thought this looked better, and was easier to follow, which is why I wanted to give it a go. I have seen people using both, but cant get this way to work. – ruffen May 31 '13 at 11:10
  • You are binding `Document` to your scope, but using `DisplayDocuments()` in the `ng-repeat` where does `DisplayDocuments()` originate from? – Mark Coleman May 31 '13 at 11:20
  • I do something simmilar in a factory. However instead of `$scope.Documents` I have `var mypromise` and I set `$scope.Documents`in the then/success part. – Eduard Gamonal May 31 '13 at 11:21
  • I have a service that should return documents, which is why I wanted to avoid setting $scope.documents in then/success part, but looks like I have to do something like that. – ruffen May 31 '13 at 11:34

4 Answers4

54

$http methods return a promise, which can't be iterated, so you have to attach the results to the scope variable through the callbacks:

$scope.documents = [];
$http.get('/Documents/DocumentsList/' + caseId)
  .then(function(result) {
    $scope.documents = result.data;
});

Now, since this defines the documents variable only after the results are fetched, you need to initialise the documents variable on scope beforehand: $scope.documents = []. Otherwise, your ng-repeat will choke.

This way, ng-repeat will first return an empty list, because documents array is empty at first, but as soon as results are received, ng-repeat will run again because the `documents``have changed in the success callback.

Also, you might want to alter you ng-repeat expression to:

<li ng-repeat="document in documents" ng-class="IsFiltered(document.Filtered)">

because if your DisplayDocuments() function is making a call to the server, than this call will be executed many times over, due to the $digest cycles.

Stewie
  • 60,366
  • 20
  • 146
  • 113
  • So I can only return a promise to scope when I am not returning an array? Displaydocuments does not make the call to the server, thanks for the heads up anyway. – ruffen May 31 '13 at 11:31
  • 1
    I meant, if you write `$scope.documents = $http.get(...)` than the `documents` will reference a promise (from the $q service), because that's what $http service returns, a promise. – Stewie May 31 '13 at 12:10
  • 3
    Yes, understood that, but i thought angular was able to resolve that promise automagically? Apparently i'm wrong. – ruffen May 31 '13 at 12:45
  • You've probably mistaken the $http service for [$resource](http://docs.angularjs.org/api/ngResource.$resource), which does return a (iterative) promise, but the promise is automatically resolved when results are back. – Stewie May 31 '13 at 13:56
  • Honestly didnt know about $resource, but it looks exactly like what im looking for. Thanks! – ruffen May 31 '13 at 17:01
  • I have been stuck with this for two hours, thanks for sharing the answer mate – Naguib Ihab Sep 27 '16 at 07:35
26

Promise returned from $http can not be binded directly (I dont exactly know why). I'm using wrapping service that works perfectly for me:

.factory('DocumentsList', function($http, $q){
    var d = $q.defer();
    $http.get('/DocumentsList').success(function(data){
        d.resolve(data);
    });
    return d.promise;
});

and bind to it in controller:

function Ctrl($scope, DocumentsList) {
    $scope.Documents = DocumentsList;
    ...
}

UPDATE!:

In Angular 1.2 auto-unwrap promises was removed. See http://docs.angularjs.org/guide/migration#templates-no-longer-automatically-unwrap-promises

vitalets
  • 4,675
  • 1
  • 35
  • 35
  • I'm still not quite sure how, but man, you've ended my misery. Thank you! – Davion Jan 15 '15 at 22:50
  • @Davion and anyone else wondering what is going on in this code, $http returns an http promise which wraps the response object, not the actual data. That's why you can't "bind" directly to it. Here, the $q service is being used to create a new promise that is being resolved to the actual data. Working with that promise, the controller can get at the actual data rather than just the http response. BTW, $http _success_ and _error_ methods are deprecated, so using _then_ is now recommended. – BitMask777 Mar 29 '16 at 22:19
6

Actually you get promise on $http.get.

Try to use followed flow:

<li ng-repeat="document in documents" ng-class="IsFiltered(document.Filtered)">
    <span><input type="checkbox" name="docChecked" id="doc_{{document.Id}}" ng-model="document.Filtered" /></span>
    <span>{{document.Name}}</span>
</li>

Where documents is your array.

$scope.documents = [];

$http.get('/Documents/DocumentsList/' + caseId).then(function(result) {
    result.data.forEach(function(val, i) { 
        $scope.documents.push(/* put data here*/);
    });
}, function(error) {
    alert(error.message);
});                       
ndsmyter
  • 6,535
  • 3
  • 22
  • 37
Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
1

Try using the success() call back

$http.get('/Documents/DocumentsList/' + caseId).success(function (result) {
    $scope.Documents = result;
});

But now since Documents is an array and not a promise, remove the ()

<li ng-repeat="document in Documents" ng-class="IsFiltered(document.Filtered)"> <span>
           <input type="checkbox" name="docChecked" id="doc_{{document.Id}}" ng-model="document.Filtered" />
        </span>
 <span>{{document.Name}}</span>

</li>
Mark Coleman
  • 40,542
  • 9
  • 81
  • 101