Using deeply nested object from JSON in AngularJS - strange behavior

10,626

Solution 1

Your $http request is asynchronous.

app.controller('MainCtrl', function($scope, JsonSvc) {
    JsonSvc.read('data.json', $scope);

    //$scope.data.level1.level2 doesn't exist yet at this point in time 
    //and throws an exception
    $scope.nestedObj = $scope.data.level1.level2;

    //$scope.data.level1.level2 doesn't exist yet at this point in time 
    //and throws an exception
    //once Angular does dirty checking this one will work since the 
    //$http request finished.
    $scope.getLen = function () {
        return $scope.data.level1.level2.length
    };
});

Since you have three scope objects that rely on that data it would be best to assign those in the call back.

app.factory('JsonSvc', function ($http) {
  return {read: function(jsonURL, scope) {
        $http.get(jsonURL).success(function (data, status) {
            scope.data = data;
      scope.nestedObj = scope.data.level1.level2;
      scope.getLen = function () {
        return scope.data.level1.level2.length;
      };
        });
    }};
});

If you do not want to set it all up on the call back, you could also use $broadcast() and $on()

app.factory('JsonSvc', function ($http, $rootScope) {
    return {
        read: function (jsonURL, scope) {
            $http.get(jsonURL).success(function (data, status) {
                scope.data = data;
                $rootScope.$broadcast("jsonDone");
            });
        }
    };
});

app.controller('MainCtrl', function ($scope, JsonSvc) {
    JsonSvc.read('data.json', $scope);
    $scope.name = "world";
    $scope.$on("jsonDone", function () {
        $scope.nestedObj = $scope.data.level1.level2;
        $scope.getLen = function () {
            return $scope.data.level1.level2.length;
        };
    });
});

Solution 2

Ray, another option is to return the $http.get call since its a promise and use the .then() function to declare $scope.nestedObj or anything else you want to do with data once it returns.

Here's my example: http://plnkr.co/edit/GbTfJ9

You can read more about promises in Angular here: http://docs.angularjs.org/api/ng.$q

Share:
10,626
Ray Shan
Author by

Ray Shan

Updated on June 04, 2022

Comments

  • Ray Shan
    Ray Shan almost 2 years

    I'm trying to understand how AngularJS sees an object from a deeply nested JSON. Here's an example plunker. The data comes from service and is assigned to $scope.data. The javascript code seems to want me to declare every level of the object first before usage, but referencing a deep level within object from the view HTML always works, and using the deep level in a function kinda works. It's rather inconsistent.

    I'm not sure if my understanding of $scope is lacking, or if this has something to do with promise objects. Advise please?

    HTML

    <body ng-controller="MainCtrl">
      Referencing nested obj in view works:
      {{data.level1.level2}}
      <br>
      Using nested obj within declared scope var doesn't work:
      {{nestedObj}}
      <br>
      Using nested obj in a function works but throws TypeError:
      {{getLen()}}
    </body>
    

    Javascript

    var app = angular.module('app', []);
    
    app.factory('JsonSvc', function ($http) {
      return {read: function(jsonURL, scope) {
            $http.get(jsonURL).success(function (data, status) {
                scope.data = data;
            });
        }};
    });
    
    app.controller('MainCtrl', function($scope, JsonSvc) {
        JsonSvc.read('data.json', $scope);
    
        // Using nested obj within declared scope var doesn't work
        // Uncomment below to break whole app
        // $scope.nestedObj = $scope.data.level1.level2;
    
        // Using nested obj in a function works but throws TypeError
        // Declaring $scope.data.level1.level2 = [] first helps here
        $scope.getLen = function () {return $scope.data.level1.level2.length};
    });
    

    JSON

    {
        "level1": {
            "level2": [
                "a",
                "b",
                "c"
            ]
        }
    }
    
  • Ray Shan
    Ray Shan almost 11 years
    Thanks Mark. I see how $http request is not finished. But how can I map JSON object to $scope.data exactly once it's finished? I thought .success would take care of it but apparently not. If the JSON is large with many levels, will each level have to be mapped manually and separately?