pbattisson
pbattisson

Reputation: 1240

Angular JS $scope.$apply giving interpolation error "TypeError: Converting circular structure to JSON"

I have written a custom angular directive as shown below:

dirs.directive('sectionInfo', function(){
    return {
        restrict: 'E',
        templateUrl: 'partials/section-home.html',
        transclude: true,
        controller: function($scope, $routeParams){
            var section = this;
            section.sectionItem = [];

            client.entries({"some params"}, 
                function(err, entries){
                    if (err) { console.log(err); return; }
                    $scope.$apply(function(){
                        section.sectionItem = entries;
                });
                }
            );
        },
        controllerAs: 'sectionCtrl'
    }
});

This is then displayed in a separate partial page which looks like:

<section-info></section-info>
<ul class="list-group">
  <li class="list-group-item" ng-repeat="entry in entriesCtrl.index_items">
    <a href="#/entries/{{entry.sys.id}}">
        <h4>{{entry.fields.title}} <small>{{entry.fields.author}}</small></h4>
    </a>
  </li>
</ul>

With the partial template code being simply:

{{sectionCtrl.sectionItem}}

When I load this page and keep the $scope$apply call in then I get an error:

Error: error:interr Interpolation Error Can't interpolate: {{sectionCtrl.sectionItem}} TypeError: Converting circular structure to JSON

When I remove the $scope.$apply call it disappears. Any idea what is causing the circular reference within the $scope.$apply call?

EDIT: Console log of content of entries and the error message.

console log

Upvotes: 2

Views: 3751

Answers (2)

Philipp Gayret
Philipp Gayret

Reputation: 5070

What's happening is you have a circular reference, meaning an object is referring to itself, through one of its properties or elements. ( In your picture, it looks like the sys field is referencing to its parent object ). To illustrate here's a sample with an object that references itself through a field, you can't convert this to regular JSON. This'll alert the error.

angular.module("so", []);

console.error = function(msg){
  console.log("Well, an error occurred, here's what it said:" + msg);
}

angular.module("so").controller("AnswerCtrl", function($scope){

  // make a thing, and put the thing in itself
  $scope.thing = { somefield: 'lalalala' };
  $scope.thing.circle= $scope.thing;

});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="so" ng-controller="AnswerCtrl">
  {{ thing }}
</div>

So the problem isn't very much related to Angular, but more that angular is using JSON.stringify or something along those lines to convert the object to a JSON string so that it can display it in the view.

What you can do is, first off - do not try to display the actual object with the circular reference. You can display parts of it just fine, but don't try to serialize a cyclic object to JSON.

If you want to display all properties though, you can use something like this answer, demoed here:

angular.module("so", []);

console.error = function(msg){
  console.log("Well, an error occurred, here's what it said:" + msg);
}

angular.module("so").controller("AnswerCtrl", function($scope){

  // make a thing, and put the thing in itself
  $scope.thing = { somefield: 'lalalala' };
  $scope.thing.circle= $scope.thing;

  var cache = [];
  $scope.viewThing = JSON.stringify($scope.thing, function(key, value) {
    if (typeof value === 'object' && value !== null) {
      if (cache.indexOf(value) !== -1) {
        return;
      }
      cache.push(value);
    }
    return value;
  });

});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="so" ng-controller="AnswerCtrl">
  {{ viewThing }}
</div>

If you have to display it, use this libary for it.

Upvotes: 4

Aleksandar Gajic
Aleksandar Gajic

Reputation: 1359

Try something like this:

var safeApply = function (scope, callback) {
    if (scope.$$phase != '$apply' && scope.$$phase != '$digest' &&
        (!scope.$root || (scope.$root.$$phase != '$apply' && scope.$root.$$phase != '$digest')))    {
        scope.$apply();
    }
    if (angular.isFunction(callback)) {
        callback();
    }
};

and you can call this

safeApply($scope, function(){
   section.sectionItem = entries;
 });

Upvotes: 1

Related Questions