Search code examples
javascriptruby-on-railsangularjsngresource

access ng-resource in function through $scope causes infinite loop


I have a Rails app and access two models through the JSON API using ng-resource.

In my template I display a list of the first model "orders":

<li ng-repeat="order in orders">
  Order {{order.id}}: 
  Product: {{showProduct(order.product_id)}}
</li>

Each "order" contains a product_id. Now I would like to access the second model ("products") and display inside the ng-repeat the correct "product" with the ID product_id of the corresponding "order".

To achieve that I thought of using a function showProduct() and use order.product_id as an argument. However when I do this it causes an infinite loop that keeps making GET requests to my database.

Here the important part of my app.js

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

app.factory('models', ['$resource', function($resource){
  var orders_model = $resource("/orders/:id.json", {id: "@id"}, {update: {method: "PUT"}});
  var products_model = $resource("/products/:id.json", {id: "@id"}, {update: {method: "PUT"}});

  var o = { 
    orders: orders_model,
    products: products_model
  };
  return o;
}]);

app.controller('OrdersCtrl', ['$scope', 'models', function($scope, models){
  $scope.orders = models.orders.query();

  $scope.showProduct = function(product_id){
    return models.products.get({id: product_id});
  };
}])

These are the errors in my console (which make sense since it's an infinite loop):

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []

Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []

The GET request in the Rails console seems fine. Maybe I'm trying to think much more complicated than I need to.


Solution

  • Yes because you have showProduct in the view, the function is called on every digest, returning each time a different promise so it breaks:

    • you create server requests several times per digest
    • each request is different from the other
    • 10 iterations are made until two values are equal or it throws an exception

    I suggest you to do:

    models.orders.query().$promise
      .then(function(orders){
        $scope.orders = orders;
        //using lodash here, I recommend you use it too
        _.each(orders, function(order){
          models.products.get({id: order.product_id }).$promise
            .then(function(product){
              order.product = product;
            })
        });
      });
    

    And in the view:

    <li ng-repeat="order in orders">
      Order {{order.id}}: 
      Product: {{order.product}}
    </li>
    

    But using this code will trigger one query for each order which is quite bad: its a N+1 anti pattern.

    2 solutions:

    • have the order include the product data directly
    • have one single request with all product ids