Introduction to $resource with $http service in angularjs

RESTful endpoints often expose CRUD operations that are accessible by calling different HTTP methods on a set of similar URLs. The code that interacts witch such endpoints is usually straightforward but tedious to write. The $resource service allows us to eliminate the repetitive code. We can also start to operate on a higher abstraction level and think of data manipulation in terms of objects (resources) and method calls instead of low-level HTTP calls.

Note

The $resource service is distributed in a separate file (angular-resource.js), and resides in a dedicated module (ngResource). To take advantage of the $resource service we need to include the angular-resource.js file and declare dependency on the ngResource module from our application’s module.

To see how easy is to interact with the RESTful endpoint using the $resource service we are going to construct an abstraction over the collection of users exposed as a RESTfulservice by the MongoLab:

angular.module('resource', ['ngResource'])

  .factory('Users', function ($resource) {

return 
$resource('https://api.mongolab.com/api/1/databases/ascrum/
   collections/users/:id', {
      apiKey:'4fb51e55e4b02e56a67b0b66',
      id:'@_id.$oid'
    });
  })

We start by registering a recipe (a factory) for the User constructor function. But notice that we don’t need to write any code for this constructor function. It is the $resource service that will prepare implementation for us.

The $resource service will generate a set of methods that make it easy to interact with a RESTFul endpoint. For example, querying for all the users in the persistence store is as simple as writing:

  .controller('ResourceCtrl', function($scope, Users){
    $scope.users = Users.query();
  });

What will happen upon the call to the User.query() method is that $resource generated code is going to prepare and issue an $http call. When a response is ready the incoming JSON string will get converted to a JavaScript array where each element of this array is of type Users.

Note

Calls to the $resource service return a generated constructor function augmented with methods to interact with a RESTful endpoint: query, get, save and delete.

angularjs requires very little information to generate a fully functional resource. Let’s examine the parameters of the $resource method to see what input is required and what can be customized:

$resource('https://api.mongolab.com/api/1/databases/ascrum/collections/users/:id', {
      apiKey:'4fb51e55e4b02e56a67b0b66',
      id:'@_id.$oid'
    });

The first argument is a URL or rather a URL pattern. The URL pattern can contain named placeholders starting with the colon character. We can specify only one URL pattern which means that all HTTP verbs should use very similar URLs.

Tip

If your back-end uses a port number as part of the URL, the port number needs to be escaped while supplying the URL pattern to the $resource call (For example, http://example.com\\:3000/api). This is required since a colon has a special meaning in the $resource‘s URL pattern.

The second argument to the $resource function allows us to define default parameters that should be sent with each request. Please note that here by “parameters” we mean both placeholders in a URL template, and standard request parameters sent as a query string. angularjs will try first to “fill holes” in the URL template, and then will add remaining parameters to the URL’s query string.

The default parameters can be either static (specified in a factory) or dynamic, taken from a resource object. Dynamic parameter values are prefixed with a @ character.

Constructor-level and instance-level methods

The $resource service automatically generates two sets of convenience methods. One set of methods will be generated on the constructor-level (class-level) for a given resource. The aim of those methods is to operate on collections of resources or cater for the situation where we don’t have any resource instance created. The other set of methods will be available on an instance of a particular resource. Those instance-level methods are responsible for interacting with one resource (one record in a data store).

Constructor-level methods

The constructor function generated by the $resource has a set of methods corresponding to different HTTP verbs:

  • Users.query(params, successcb, errorcb): It issues an HTTP GET request and expects an array in the JSON response. It is used to retrieve a collection of items.
  • Users.get(params, successcb, errorcb): It issues an HTTP GET request and expects an object in the JSON response. It is used to retrieve a single item.
  • Users.save(params, payloadData, successcb, errorcb): It issues an HTTP POST request with request body generated from the payload.
  • Users.delete(params,successcb, errorcb) (and its alias: Users.remove): It issues an HTTP DELETE request.

For all the methods listed earlier the successcb and errorcb denote a success and error callback functions, respectively. The params argument allows us to specify per-action parameters that are going to end up either as part of the URL or as a parameter in a query string. Lastly, the payloadData argument allows us to specify the HTTP request body where appropriate (POST and PUT requests).

Instance level methods

The $resource service will not only generate a constructor function, but also will add prototype (instance) level methods. The instance level methods are equivalents of their class-level counterparts but operate of a single instance. For example, a user can be deleted either by calling:

Users.delete({}, user);

Or by invoking a method on the user’s instance like:

user.$delete();

Instance-level methods are very convenient, and allow us write concise code-manipulating resources. Let’s see another example of saving a new user:

var user = new Users({
  name:'Superhero'
});
user.$save();

This could be re-written using the class-level save method:

var user = {
  name:'Superhero'
};
Users.save(user);

Tip

The $resource factory generates both class-level and instance level methods. The instance level-methods are prefixed with the $ character. Both the methods have the equivalent functionality so it is up to you to choose the more convenient form depending on your use-case.

Custom methods

By default the $resource factory generates a set of methods that is sufficient for typical use-cases. If a back-end uses different HTTP verbs for certain operations (For example, PUT or PATCH) it is relatively easy to add custom methods on a resource level.

Tip

By default the $resource factory doesn’t generate any method corresponding to HTTP PUT requests. If your back-end maps any operations to HTTP PUT requests you will have to add those methods manually.

For example, the MongoLab REST API is using the HTTP POST method to create new items but the PUT method must be used to update existing entries. Let’s see how to define a custom update method (both the class-level update and the instance-level $update):

.factory('Users', function ($resource) {
  return $resource('https://api.mongolab.com/api/1/databases/ascrum/collections/users/:id', {
    apiKey:'4fb51e55e4b02e56a67b0b66',
    id:'@_id.$oid'
  }, {
    update: {method:'PUT'}
  });
})

As you can see defining a new method is as simple as supplying a third parameter to the $resource factory function. The parameter must be an object of the following form:

action: {method:?, params:?, isArray:?, headers:?}

The action key is a new method name to be generated. A generated method will issue a HTTP request specified by method, params are holding default parameters specific to this particular action and the isArray specifies, if data returned from a back-end represent a collection (an array) or a single object. It is also possible to specify custom HTTP headers.

The $resource service can only work with JavaScript arrays and objects as data received from a back-end. Single values (primitive types) are not supported. Methods returning a collection (ones flagged with isArray) must return a JavaScript array. Arrays wrapped inside a JavaScript object won’t be processed as expected.

Adding behavior to resource objects

The $resource factory is generating constructor functions that can be used as any other JavaScript constructor to create new resource instances using the new keyword. But we can also extend prototype of this constructor to add new behavior to resource objects. Let’s say that we want to have a new method on the user level outputting a full name based on a first and last name. Here is the recommended way of achieving this:

.factory('Users', function ($resource) {
    var Users = $resource('https://api.mongolab.com/api/1/databases/ascrum/collections/users/:id', {
      apiKey:'4fb51e55e4b02e56a67b0b66',
      id:'@_id.$oid'
    }, {
      update: {method:'PUT'}
    });

    Users.prototype.getFullName = function() {
      return this.firstName + ' ' + this.lastName;
    };

    return Users;
})

Adding new methods on the class (constructor) level is also possible. Since, in JavaScript a function is a first-class object, we can define new methods on a constructor function. This way we can add custom methods “by hand” instead of relying on the angularjs automatic method generation. This might prove useful, if we need some non-standard logic in one of the resources methods. For example, the MongoLab’s REST API requires that the identifier of an object is removed from a payload while issuing PUT (update) requests.

$resource creates asynchronous methods

Let’s have a second look at the query method example:

$scope.users = Users.query();

We might get the impression that generated resources behave in the synchronous way (we are not using any callbacks or promises here). In reality the query method call is asynchronous and angularjs uses a smart trick to make it looks like synchronous.

What is going on here is that angularjswill return immediately from a call to Users.query()with an empty array as a result. Then, when the asynchronous call is successful, and real data arrives from the server, the array will get updated with the data. angularjs will simply keep a reference to an array returned at first, and will fill it in when data is available. This trick works in angularjs since upon data arrival the content of the returned array will change and templates will get refreshed automatically.

But don’t get mistaken, the $resource calls are asynchronous. This is often a source of confusion, as you might want to try to write the following code (or access the initial array in any other way):

$scope.users = Users.query();
console.log($scope.users.length);

And it doesn’t work as expected!

Fortunately it is possible to use callbacks in the methods generated by the $resource factory and rewrite the preceding code to make it behave as intended:

Users.query(function(users){
      $scope.users = users;
      console.log($scope.users.length);
    });

Note

Methods generated by the $resource factory are asynchronous, even if angularjs is using a clever trick, and the syntax might suggest that we are dealing with the synchronous methods.

Limitations of the $resource service

The $resource factory is a handy service, and lets us to start talking to RESTful back-ends in virtually no time. But the problem with $resource is that it is a generic service; not tailored to any particular back-end needs. As such it takes some assumptions that might not be true for the back-end of our choice.

If the $resource factory works for your back-end and web-application, that’s great. There are many use-cases where $resource might be enough, but for more sophisticated applications it is often better to use lower-level $http service.

 

Custom REST adapters with $http

The $resource factory is very handy, but if you hit its limitations it is relatively easy to create a custom, $resource-like factory based on the $http service. By taking time to write a custom resource factory we can gain full control over URLs and data pre/post processing. As a bonus we would no longer need to include the angular-resource.js file and thus save few KB of the total page weight.

What follows is a simplified example of a custom resource-like factory dedicated to the MongoLab RESTful API. Familiarity with the Promise API is the key to understanding this implementation:

angular.module('mongolabResource', [])

.factory('mongolabResource', function ($http, MONGOLAB_CONFIG) {

  return function (collectionName) {

    //basic configuration
    var collectionUrl =
      'https://api.mongolab.com/api/1/databases/' +
        MONGOLAB_CONFIG.DB_NAME +
        '/collections/' + collectionName;

    var defaultParams = {apiKey:MONGOLAB_CONFIG.API_KEY};

    //utility methods
    var getId = function (data) {
      return data._id.$oid;
    };

    //a constructor for new resources
    var Resource = function (data) {
      angular.extend(this, data);
    };

    Resource.query = function (params) {
      return $http.get(collectionUrl, {
        params:angular.extend({q:JSON.stringify({} || params)}, defaultParams)
      }).then(function (response) {
          var result = [];
          angular.forEach(response.data, function (value, key) {
            result[key] = new Resource(value);
          });
          return result;
        });
    };

    Resource.save = function (data) {
      return $http.post(collectionUrl, data, {params:defaultParams})
        .then(function (response) {
          return new Resource(data);
        });
    };

    Resource.prototype.$save = function (data) {
   return Resource.save(this);

    };

    Resource.remove = function (data) {
      return $http.delete(collectionUrl + '', defaultParams)
        .then(function (response) {
return new Resource(data);
        });
    };

    Resource.prototype.$remove = function (data) {
return Resource.remove(this);
    };

    //other CRUD methods go here

    //convenience methods
    Resource.prototype.$id = function () {
      return getId(this);
    };

    return Resource;
  };
});

The example code starts by declaring a new module (mongolabResource) and a factory (mongolabResource) accepting a configuration object (MONGOLAB_CONFIG) those are the parts that should look familiar by now. Based on a provided configuration object we can prepare a URL to be used by a resource. Here we are in total control of how a URL is created and manipulated.

Then, the Resource constructor is declared, so we can create new resource objects from existing data. What follows is a definition of several methods: query, save and remove. Those methods are defined on the constructor (class) level, but instance-level methods are also declared (where appropriate) to follow the same convention as the original $resource implementation. Providing an instance-level method is as easy as delegating to the class-level one:

Resource.prototype.$save = function (data) {
      return Resource.save(this);
};

Usage of promise chaining is the crucial part of the custom resource implementation. The then method is always called on a promise returned from a $http call, and a resulting promise is returned to the client. A success callback on a promise returned from a $http call is used to register post-processing logic. For example, in the query method implementation we post-process raw JSON returned from a back-end, and create resource instances. Here, once again we’ve got full control over data extraction process from a response.

Let’s examine how a new resource factory could be used:

angular.module('customResourceDemo', ['mongolabResource'])
  .constant('MONGOLAB_CONFIG', {
    DB_NAME: 'ascrum',
    API_KEY: '4fb51e55e4b02e56a67b0b66'
  })

  .factory('Users', function (mongolabResource) {
    return mongolabResource('users');
  })

  .controller('CustomResourceCtrl', function ($scope, Users,
    Projects) {

    Users.query().then(function(users){
      $scope.users = users;
    });
  });

The usage of our custom resource doesn’t differ much from the original $resource equivalent. To start with we need to declare a dependency on the module where a custom resource factory is defined (mongolabResource). Next, we need to provide required configuration options in form of a constant. As soon as the initial setup is done we can define actual resources created by a custom factory. It is as easy as invoking the mongolabResource factory function and passing a MongoDB collection name as an argument.

During application’s run-time a newly defined resource constructor (here: Users) can be injected as any other dependency. Then an injected constructor can be used to invoke either class-level or instance-level methods. An example of the later is shown here:

$scope.addSuperhero = function () {
  new Users({name: 'Superhero'}).$save();
};

The best part of switching to a custom, $http-based version of a resource factory is that we can enjoy the full power of the Promise API.

Deven Rathore

Deven is an Entrepreneur, and Full-stack developer, Constantly learning and experiencing new things. He currently runs CodeSource.io and Dunebook.com.

Published by
Deven Rathore

Recent Posts

Are There any Similarities Between Web Design and Art?

You may be surprised at how many of the same skills are involved!  Let’s get…

1 day ago

Tips on Increasing your organic traffic on WordPress

Introduction  As more and more people are using online services nowadays, most of the business…

2 days ago

Five Reasons You Should Start a Social Media Campaign

Small businesses need all the advertisements they can get. Traditional avenues for advertising, such as…

3 days ago

Top 10 SEO Writing Tips for Beginners 2021

Search Engine Optimization. It’s complicated, to say the least. Search engines, like Google, are constantly…

2 weeks ago

Should you become a freelancer in 2021? Pros and Cons

Freelancing and working from home was long considered idyllic by many, but the global pandemic…

2 weeks ago

The Leading Renewable Energy Trends in 2021

The past year saw slowdowns in the ongoing shift toward renewable energy in many countries…

2 weeks ago