Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS ng-repeat with data from service

Originally in my app, I created controllers with very basic $http calls to get a resource by getting the ID of an object from the url ($routeParams). Ng-repeat display the results correctly.

However, I noticed refreshing in a later view (different controller) wiped out the data and broke the page. So, I created a function on the service to be used in multiple controllers, to check whether the data has is available and to react as follows:

1) If the resource is defined, return it (no API call) 2) If the resource is not defined, get the id from the url and get it from the API 3) If the resource is not defined & you can't get the ID, just return false.

However, this broke the code: the template rendered before the service returned the data, and ng-repeat did not update. The code looks like this:

angular.module('myApp', ['ngCookies'])
    .config(...)
    .service('myService', ['$cookies', '$http', function($cookies, $http) {
        myData = {};

        return {
            getData:function(dataID) {
                if(myData.name) {return myData);
                else if (dataID && dataID !== '') {
                    $http.get('/api/data/' + dataID)
                        .success(function(data) {
                            myData = data.object;
                            $cookies.dataID = data.object.id;
                            return myData;
                        }
                }
                else { return false; }
            }
        }
    }]);

function myCtrl($scope, $http, $routeParams, myService) {
    $scope.data = myService.getData($routeParams.dataID);

    ...
}

And here's the template. It's in jade, which means rather than angle brackets, you just list the element with parameters in parenthesis right after, and content after the parenthesis.

h2 My heading
ul
    li(ng-repeat='option in data')
        a(href="#", ng-click='someFuncInCtrl(option.name)')  {{ option.name }}

When the controller did the $http.get itself, the ng-repeat worked fine because the $scope was updated in the ".success" callback. Now that there's a service that returns the data after a slight delay, "$scope.data" is just undefined, the ng-repeat list is empty.

I used a console.log to check myData right before return "return myData", and the myData is working, it just isn't returned in time, and for whatever reason the list is not updating whenever $scope does get the data.

I looked a using $routeProvider's resolve... but that makes getting the ID from the url challenging, as the resolve object doesn't seem to have access to $routeParams. I know that $scope.$apply is supposed to help update the scope when it's altered by outside functions... but I have no clue where to put it. The most similar problem on SO didn't use a service.

I tried:

$scope.$apply($scope.data = myService.getData($routeParams.dataID));

And

$scope.$apply(function() {
    $scope.data = myService($routeParams.dataID);
});

Both times I only got Error: $digest already in progress.

like image 284
ansorensen Avatar asked Feb 18 '13 23:02

ansorensen


People also ask

What can I use instead of NG-repeat?

You can consider using transclusion inside a custom directive, to achieve the behavior you are looking for without using ng-repeat.

How do you pass an index in NG-repeat?

Each ng-repeat creates a child scope with the passed data, and also adds an additional $index variable in that scope. So what you need to do is reach up to the parent scope, and use that $index .

What is data ng-repeat?

Definition and Usage The ng-repeat directive repeats a set of HTML, a given number of times. The set of HTML will be repeated once per item in a collection. The collection must be an array or an object. Note: Each instance of the repetition is given its own scope, which consist of the current item.

What is difference between ng-repeat and Ng options?

ng-options is the directive which is designed specifically to populate the items of a dropdown list. One major advantage using ng-options for the dropdown is, it allows us to pass the selected value to be an object. Whereas, using ng-repeat the selected value can only be string.


1 Answers

The problem is on the way you interact with the service. Since your getData function can return both synchronous and/or asynchronous information, you can't just use normal return(s).

$http.get('/api/data/' + dataID)
    .success(function(data) {
        myData = data.object;
        $cookies.dataID = data.object.id;
        return myData;
    });

The return on the above snippet will not return anything from getData because it will be executed on the context of the $http.get success callback (and not on the getData call stack).

The best approach for handling sync and async service requests is to use promises.

Your getData function should look something like this:

getData:function(dataID) {
    var deferred = $q.defer();
    if(myData.name) {
       deferred.resolve(myData);
    } else if (dataID && dataID !== '') {
        $http.get('/api/data/' + dataID)
            .success(function(data) {
                 myData = data.object;
                 $cookies.dataID = data.object.id;
                 deferred.resolve(myData);
                 // update angular's scopes
                 $rootScope.$$phase || $rootScope.$apply();
             });
    } else { 
       deferred.reject();
    }

    return deferred.promise;
}

Note: You need to inject the $rootScope on your service.

And on your controller:

function myCtrl($scope, $http, $routeParams, myService) {
    myService.getData($routeParams.dataID).then(function(data) {
        // request was successful
        $scope.data = data;        
    }, function() {
        // request failed (same as your 'return false')
        $scope.data = undefined;
    });
}
like image 146
bmleite Avatar answered Oct 12 '22 19:10

bmleite