Search code examples
angularjsdomreactjsclonengreact

circular objects, illegal isArrayLike() invocation and Infinite $digest loop creating a <div> with ngReact


[UPDATED Sept, 24]

I am trying to use ngReact instead of ngRepeat to improve performance when I modify my $watched array of objects.

For each object (a map marker) contained in the controller, I want to create a <button>, with the marker.title as text. For this purpose, I crated a React component that $watches an array of markers. Such a contains a list of , one for each marker. I supposed that the component will result changed only if I modify the list of markers, and then the list of buttons. However this is not the case.

[TypeError]: Illegal invocation
  at isArrayLike (angular.js:274)
  at forEach (angular.js:328)
  at copy (angular.js:886)

[Error]: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
http://errors.angularjs.org/1.4.0-rc.0/$rootScope/infdig?p0=10&p1=%5B%5D
    at REGEX_STRING_REGEXP (angular.js:68)
    at Scope.parent.$get.Scope.$digest (angular.js:15340)
    at Scope.parent.$get.Scope.$apply (angular.js:15570)
    at (index):95

It seems that the list of the <button> created by the React component results always different, even if made by the same objects.

Now the weird part.. Actually,I add markers to the map using Json objects. When I add a Json object, the map create a Marker object, not jsonable cause it has a circular structure. I add this Marker object to my $watched array, to create the relative <button>... Maybe this is the cause of the illegal isArrayLike() error?

Here it is my React component (at the end of my post, there is the JSfiddle link)

HTML

    <!-- m_list is an attribute of $rootScope -->
    <poi-list list="m_list" />

JS

.value( "PoiList", React.createClass( {

  propTypes : {

    list: React.PropTypes.object.isRequired
  },

  render: function() 
   {
    var markers = this.props.list.map( function( marker, i ) 
        {//for each marker, create a button
         return React.DOM.button(  {
                                    className:'btn btn-default'                               
                                   }, marker.title
                                 ) ; 
        } );
    return React.DOM.div({}, markers);
    }


}))

.directive( 'poiList', function( reactDirective ) {
  return reactDirective( 'PoiList' );
} );

this is a JSFiddle of my problem. A brief description:

  • there are 2 arrays in the $rootScope. temp_list is used to temporarily push all the markers one by one. m_list is $watched by the react component.
  • there is a MapBox directive with its controller. For each marker added to the map, it will be pushed in the temp_list.
  • once all the markers have been loaded and temp_list is complete, this is cloned to m_list, so the react component can be updated.

thank you


Solution

  • I found a solution! The problem somehow is connected to the use of the map. In the watched array, I am not adding the Json markers, but I am adding the object marker created by MapBox.

    map.featureLayer.setGeoJSON(json_markers_list);  // I pass an array of Json markers
    map.featureLayer.on('layeradd', function(e) 
                    {
                         var marker = e.layer;        // marker is anymore a json obj
                         ctrl.pushMarker(marker);  // marker now has a cyclic structure
                        });
    

    The object marker created by MapBox, has a cyclic structure. For this reason, React cannot know if the old $watched array has changed since the last check, because it cannot perform isArrayLike() when cycle structure is present. So it rises an error (different in any browser) and considers the array always changed, generating the $digest infinite iteration.

    My solution is using one array and one map. The array is $watched with just the information I need to display in the button

    {'title': 'marker title', 'id':00001}
    

    the map contains the real marker objects

    {'00001' : {markerObj}}
    

    and this is a working JSFiddle ! Click on a button and the marker object will be selected.

    This question and solution is also poster on the forum of the official ng-react project