Deprecated Behaviour

The inane, sometimes insane, ramblings from the mind of Brenton Alker.

Post-fetch Manipulation (Sorting) of ngResources in AngularJS

Just a quick tip that I found useful in a recent Angular.js application.

I was trying to randomize the order of elements within a list retrieved from a ngResource (answers in a quiz or poll application). The naive approach would be to write the code as if the resource fetch is synchronous.

1
2
3
4
5
6
angular.module('myApp').controller('MyCtrl', function ($scope, $resource, $routeParams) {
  var shuffle = function (list) { /* ... */ };
  var repo = $resource('/api/question/:id', {id: '@id'});
  $scope.question = repo.get({id: $routeParams.id});
  shuffle(question.anwers);
});

The problem that will soon become apparent, is that the get is not synchronous. So, when the shuffle(question.answer) is called, the question object is not yet populated.

One approach that I encountered to circumvent the issue is to use the built in orderBy filter with a callback that randomly chooses the order. This works, because the filter is run when the returning data triggers a new digest. This may be good enough in some circumstances. The problem I encountered with this approach however, is the list is re-sorted every digest cycle. So, if there is anything much going on in the scope, the list will re-sort itself causing the bound elements in the page to jump around.

The solution for me, was to sort the list once, after it is retrieved. This can be accomplished by passing a callback, as the second argument to $resource.get, that will be called with the returned data once it becomes available. This allows you to perform any required manipulation on the data before it is assigned to the $scope.

1
2
3
4
5
6
7
angular.module('myApp').controller('MyCtrl', function ($scope, $resource, $routeParams) {
  var shuffle = function (list) { /* ... */ };
  var repo = $resource('/api/question/:id', {id: '@id'});
  $scope.question = repo.get({id: $routeParams.id}, function (question) {
      shuffle(question.anwers);
  });
});

In this instance, the answers to the question are shuffled, but the pattern is useful any time you want to perform an action only after the data if fetched. This may include calculating aggregates or even fetching extra, dependant data.

As an aside, I don’t believe this manipulation belongs in the controller and should probably be encapsulated in a service that the controller consumes. I will have a look at using “fat” services and “lean” controllers in a future post.