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.