Using ngResource with AngularJS

In my first post on AngularJS I created a simple Todo application with a Rails/Mongo RESTful server. In this application I was doing all my ajax calls manually (which is really easy in Angular), which meant most of my controller methods looked like this:

$scope.addTodo = function() {
  var description = $scope.newTodo.description;
  $http.post('/todo.json', { description: description }).success(function() {
    $scope.newTodo.description = '';
    $location.path("list");
  });
}

This obviously works just fine and it’s a pretty simple implementation. However if you’re implementing a RESTful API on your server Angular makes it even easier for you with ngResource. Here is the description in the Angular documentation:

A factory which creates a resource object that lets you interact with RESTful server-side data sources.

The returned resource object has action methods which provide high-level behaviors without the need to interact with the low level $http service.

This means that if we’re using ngResource correctly (and we have a RESTful API) we shouldn’t see any $http calls in our Angular code.

Using ngResource

To get started, we need to reference the resource library.

<%= javascript_include_tag "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular-resource.min.js" %>

We also need to specify the dependency on the resource module when we create our application.

var todoApp = angular.module('todoApp', ['ngResource'])

Now we need to create a factory for the resource. We don’t really need to use a factory (we could just create the resource directly in the controller), but it’s much neater to do so.

todoApp.factory('todoFactory', function($resource) {
  return $resource('/todo/:todoId', { todoId:'@_id' });
});

The first parameter of the $resource function is the URL to the server-side resource. I am also specifying that the URL parameter :todoId should default to the _id property on the object, if it exists. This is actually a little weird and a bit messy. Remember that I’m using Mongo on the server – Mongo objects will by default have an _id property (instead of id) when converted to JSON. If I was doing this in a real application I would probably use something like Rabl views to make sure the JSON representation has an id property instead of _id, but for now it works just fine.

Now we can go ahead and reference our factory in the controller.

function TodoController($scope, $location, todoFactory) {

All the server-side interactions now become one-liners. Here is the code for loading all the todo items without using ngResource.

function loadTodos() {
  $http.get('/todo.json').success(function(data) {
    $scope.items = data;
  });
}

Here is the code using ngResource.

function loadTodos() {
  $scope.items = todoFactory.query() 
}

Even better, here is the code for adding a todo item without using ngResource.

$scope.addTodo = function() {
  var description = $scope.newTodo.description;
  $http.post('/todo.json', { description: description }).success(function() {
    $scope.newTodo.description = '';
    $location.path("list");
  });
}

Here is the equivalent code using ngResource.

$scope.addTodo = function() {
  todoFactory.save($scope.newTodoModel, backToList);
}

Most of my code has now been reduced to specifying what to do with the resource and what view to render when we’re done. I think I’m probably still missing a few tricks, but so far I’m really impressed.

Doing an update with PUT instead of POST

For some reason that I don’t really understand Angular will do a POST when updating a resource. This caused my Rails server to create a new todo item every time I wanted to update an item. After some digging I found this was actually pretty easy to fix.

In our factory, we just need to specify the verb for update:

todoApp.factory('todoFactory', function($resource) {
  return $resource('/todo/:todoId', 
    { todoId:'@_id' }, 
    { update: { method: 'PUT' }}
  );
});

Now calling update on the todo item will result in a PUT instead of a POST. Problem solved.

$scope.updateTodo = function(todo) {
  todo.$update(backToList);
}

As I said, I think I’m still missing a few tricks and my implementation will probably improve over the next few weeks as my understanding of Angular improves. All the code is available on GitHub. Happy coding.

Tags: AngularJS, JavaScript

  1. [...] Using ngResource with AngularJS (Jaco Pretorius) [...]

  2. Thanks for the write up. This should get me started on ngResource!

  3. CJ says:

    This is the best simple introduction to ngResource that I’ve seen. Everything else I’ve seen uses a terser syntax that doesn’t match the angular module mindset (function instead of factory). Thanks for putting the time into writing it up.

  4. Bcz says:

    Really neat, thank you !

  5. Hey, you might help me…hopefully you can, I’m starting with angular…have been working with JSF for a long time, but I believe that AngularJS works better with json.

    For that I am translating my jsf webapp to use angular.

    SO,
    I have a ng-click=”loadDefaultsCurrency()” that calls my ctrl.

    $scope.loadDefaultsCurrency = function() {
    console.log(‘started loadDefaultsCurrency’);
    console.log(“locale: “+$scope.lang);

    currencyService.getbylocale($.param({locale: ‘en’}), function(result) {

    console.log(result);

    for (var int = 0; int < result.length; int++) {
    console.log('result['+int+']: '+result[int].name);
    }
    });
    console.log('ended loadDefaultsCurrency');
    };

    and this ctrl calls my service

    factory('currencyService', function($resource){
    console.log('get currency service.');
    return $resource('/cashtrackAPI/rest/currency/:action', {},
    {
    getbylocale: {
    method: 'GET',
    params: {'action' : '@locale'},
    isArray:true
    }
    }
    );
    });

    my problem is that I never get the parameter @locale from my ctrl, what am I doing wrong?

    thanks in advance,
    Anderson

  6. @Anderson Have you tried asking your question on StackOverflow?