0

I am new to AngularJS and I am trying to put together a practice WebApp, In the earlier versions of My App I had a single Controller and I used the $http Service to fetch Data from a JSON file like so

var App = angular.module('testApp', []);
App.controller('firstCtrl', ['$scope', '$http', function($scope, $http) {
    $scope.items = [];
    $http.get('data.json').success(function(data) {
      $scope.items = data;
    });
}]);

The $http Service worked fine, Then as I progressed I had Multiple Controllers and to Share the Data between the Controllers I moved the $http Service to a Factory like so

var App = angular.module('testApp', []);

App.factory('DataFactory', ['$http', function($http) {
    var DataFactory = {};
    var items = [];
    $http.get('data.json').success(function(data) {
      items = data;
    });
    DataFactory.data = function() {
        return items;
    };
    return DataFactory;
}]);

App.controller('firstCtrl', ['$scope', 'DataFactory',function($scope, DataFactory) {
    $scope.items = [];
    $scope.items = DataFactory.data();
}]);

And here is the HTML

<div ng-controller="firstCtrl">
  <ul>
    <li ng-repeat="item in items">{{item.name}}</li>
  </ul>
</div>

I don't know why for some reason the Data is not being Displayed after I moved the $http Service to a Factory, What am I doing wrong, I don't see any errors in the Console

2 Answers 2

1

As one learns a lot in 18 months, I would like to update my answer. The below example is how I would solve the problem today. (See bottom for original solution)

function dataFactory($http) {
    //Service interface, all properties and methods will be set to this object.
    var dataFactory={};

    //Instead of using $q, the function will just return the http-promise containing the response data.
    dataFactory.getItems=function() {
        return $http
            .get('data.json')
            .then(function(response) {
                return response.data; 
            });
    }

    //Return object containing the service interface.
    return dataFactory;
}
//Use $inject property to specifiy your DI objects, rather than using array syntax.
dataFactory.$inject=['$http'];

function firstController(DataFactory) {
    //Use this with controllerAs instead of injecting $scope.
    var vm=this;
    vm.items=[];

    DataFactory
        .getItems()
        .then(function(items) {
            vm.items=items;
        }, function(err) {
            //error handler
            alert("Got an error");
        })
}
//same here, use $inject property.
firstController.$inject=['dataFactory'];

angular
    .module('testApp', [])
    .factory('DataFactory', dataFactory)
    .controller('FirstController', firstController);

And the HTML

 <!-- Use controller as syntax -->
 <div ng-controller="firstController as first">
     <ul>
         <!-- Reference the controller by value given in controller as statement -->
         <li ng-repeat="item in first.items">{{item.name}}</li>
     </ul>
 </div>

This is how I would write the code. However, this is not how I would solve it. I wouldn't make any changes the data service, But I would change the implementation of the controller. Either I would resolve the items data through the router, or I would bundle the controller and html as a directive, or as of 1.5 a component.

Using a directive

function itemsDirective() {
    function controller(DataFactory) {
        var vm=this;
        vm.items=[];

        DataFactory
            .getItems()
            .then(function(items) {
                vm.items=items;
            }, function(err) {
                //error handler
                alert("Got an error");
            })
    }
    controller.$inject=['dataFactory'];

    return {
        restrict:'E',
        template:'<div ng-controller="firstController as first">
            <ul>
                <li ng-repeat="item in first.items">{{item.name}}</li>
            </ul>
        </div>',
        controller: controller,
        controllerAs: 'first'
    }
}

angular
    .module('testApp')
    .directive('itemsDirective', itemsDirective);

Old answer (Jul 23 '14 at 20:26)

Because the value doesn't get set before it returns it's value. Having that said you might wanna restructure your Service(factory), also use $q to handle the promise. Consider the following example:

var App = angular.module('testApp', []);

App.factory('DataFactory', ['$http', '$q', function($http, $q) {
    var getItems = function() {
        var deffered = $q.defer();

        $http.get('data.json').success(function(data) {
             deffered.resolve(data);
        });

        return deffered.promise;
    };

    return {
       getItems: getItems
    };
}]);

App.controller('firstCtrl', ['$scope', 'DataFactory',function($scope, DataFactory) {
   $scope.items = DataFactory.getItems();
}]);

It's common practice to use $q while working with async tasks such as http-request.

Sign up to request clarification or add additional context in comments.

2 Comments

At least in AngularJS 1.2+ it's not possible to directly bind to a promise. You'll have to unwrap the promise in the controller and set the $scope.items variable in the then(...) callback. So it's enough to return the prmise from the $http.get and unwrap it in the controller. No custom promise logic required (which actually is unnecessary anyway).
$http also is set up as a promise by default. It's using 'success' and 'error' in place of $q's 'then'.
1

A simplified solution based on the Answer from cbass, unwrapping the promise in the controller:

var App = angular.module('testApp', []);

App.factory('DataFactory', ['$http', function($http) {
    var getItems = function() {
        return $http.get('data.json');
    };

    return {
       getItems: getItems
    };
}]);

App.controller('firstCtrl', ['$scope', 'DataFactory',function($scope, DataFactory) {
   DataFactory.getItems().success(function(result){
       $scope.items = result;
   });
}]);

3 Comments

What if I have another Controller which needs the items? Do I have to redo it to get the items in this new Controller? If Yes then won't I be making another http request?
Either of the soluations above will make a http-request apon calling the getItems function. You can however choose to cache the response in the service and then reference that in your controller(s). In this case you might wanna make the http-request apon initalization of the service and/or continuosly update the cache.
@abhi yes, with this solution every controller makes it's own request. But you can use the cache: true option to cache the result. So the second controller will load the data from the cache. see the Caching section in the docs here: docs.angularjs.org/api/ng/service/$http

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.