Ronald91
Ronald91

Reputation: 1776

Traversing JSON data from Angular Factory

I have an angular controller that uses a factory to return a response from a JSON file. When I try to traverse the data returned I get the following error seen in the comment. However if I use the realArticleData object inside of HTML within angular brackets it transverses perfectly. I believe it has something to do with the promise returned by the factory but I am not sure.

Controller Snippet:

function SpecialOrderItemsCtrl($scope, $location, articleDataService) {
    $scope.realArticleData = articleDataService.getArticles();
    //This throws TypeError: Cannot read property 'thdCustomer' of undefined
    $scope.custInfo = $scope.realArticleData.articles.thdCustomer;
}

HTML Snippet:

<div ng-controller=SpecialOrderItemsCtrl>
<label>Raw Article JSON</label>
<p>{{realArticleData}}</p>
<label>Json transversed by one level</label>
<p>{{realArticleData.articles}}</p>
<label>THD CUstomer </label>
<p>{{realArticleData.articles.thdCustomer}}</p>
</div>

Factory:

function articleDataService($rootScope, $http) {
    articleDataService.data = {};
    articleDataService.getArticles = function() {
    $http.get('articleData.json')
        .success(function(data) {
            articleDataService.data.articles = data;
        });
        return articleDataService.data;
    };
    return articleDataService;
}
sendDesign.factory('articleDataService', articleDataService);

Upvotes: 2

Views: 1200

Answers (3)

joemaller
joemaller

Reputation: 20576

The key point to remember is that promises do not return values, they do something with a value when or if a value arrives.

This answer is based on answers from @VladimirGurovich and @tymeJV.

Here's a simple factory:

var app = angular.module('myApp', []);    

app.factory('Names', ['$http', function($http) {
  return {
    url: 'names.json',
    fetch: function() {
      return $http.get(this.url, {cache: true}).then(function(json) {
        return json.data;
      });
    }
  };
}]);

The Names factory itself returns an object. The fetch method returns the promise chain from $http.get. This uses .then() instead of .success() so the result can be chained. The factory could return the $http promise directly, but giving the operation a name seems clearer to me.

@VladimirGurovich's answer handles caching manually, but Angular can do that for us, just send a config object like {config: true} to $http.get along with the url.

Here's a simple little controller which depends on data from the Names factory:

app.controller('MainCtrl', ["$scope", "Names", function($scope, Names) {
    Names.fetch().then(function(data) {
        $scope.names = data.names;
    });
}]);

When the Names.fetch() promise completes, $scope.names is populated with the returned data.

Here's a Plunker based on this: http://plnkr.co/edit/nMx1XL

Upvotes: 2

Vlad Gurovich
Vlad Gurovich

Reputation: 8463

You are dealing with promises. Therefore, you need to deal with them asynchronously. as in

angular.module('plunker', []).controller('SpecialOrderItemsCtrl', ['$scope', 'articleDataService',  function($scope, articleDataService){
  // when promise is resolved, set the scope variable
  articleDataService.getArticles().then(function(articles){
      // store any data from json that you want on the scope for later access in your templates
      $scope.articles = articles;
  })
}]).factory('articleDataService',['$http', '$q', function($http, $q){
  var data = null;
  return {
    getArticles: function() {
      var promise;
      if(data === null) {
        promise = $http.get('articleData.json').then(function(response){
          // cache the response
          data = response;
          return response;
        });
      } else {
        var deferred = $q.defer();
        // promise is immediately resolved with the cached data
        // this way both cached and async data can be handled consistently
        promise = deferred.promise;
      }
      return promise;
    }
  } ;
}]);

Plunkr link: http://plnkr.co/edit/7Oas2T

Upvotes: 4

tymeJV
tymeJV

Reputation: 104775

Change your service to:

articleDataService.getArticles = function() {
    $http.get('articleData.json').then(function(result) {
        return result.data
    });
}

And in your controller:

articleDataService.getArticles().then(function(data) {
    $scope.realArticleData = data;
    $scope.custInfo = $scope.realArticleData.articles.thdCustomer;
});

You need to follow the promise pattern back to the controller, else you're not waiting for your call to finish and you'll be receiving undefined

Upvotes: 2

Related Questions