Using an AngularJS Factory to Interact with a RESTful Service

Dan Wahlin

by Dan Wahlin on 5/6/2014

Share this:
Print

Article Details

Date Revised:
5/6/2014


angular

AngularJS provides a great framework for building robust Single Page Applications (SPAs) and provides built-in support for routing, MV*-style programming, services and factories, modules, testing, and much more. Although Angular can consume virtually any HTTP resource, in this post I’m going to focus on using it to consume a RESTful API created using ASP.NET Web API (note that any back-end service could be used). If you’ve worked with jQuery before then you’re used to calls such as $.getJSON() or $.ajax(). Although Angular plays really well with jQuery, it has built-in HTTP functionality that can be used out of the box and a way to encapsulate data functionality using factories or services.

Here’s a quick review of some of the key players in AngularJS. I’ll touch on modules, routes, factories, and controllers in this post and discuss how data functionality (such as RESTful service calls) can be encapsulated in factories that can be called from controllers.

big

Let’s kick things off by taking a look at a basic backend service written using ASP.NET Web API and then walk through several AngularJS features including a factory that talks to the service.

 Creating a RESTful Service

ASP.NET Web API is a great back-end framework for exposing data to a variety of clients. Although I’ll focus on how JSON data can be exposed in this post, it can be used to serve up a variety of formats ranging from XML to images to custom formats. Here’s an example of a simple service that exposes customer and order resources to clients. When the service is called from Web clients it will automatically return JSON data.

public class CustomersController : ApiController
    {
        ICustomerRepository _Repository;

        public CustomersController()
        {
            //CustomerRepository could be injected if desired
            _Repository = new CustomerRepository();
        }

        // GET api/customers
        public HttpResponseMessage Get()
        {
            var custs = _Repository.GetCustomers();
            if (custs == null) throw new HttpResponseException(HttpStatusCode.NotFound);
            return Request.CreateResponse<IEnumerable<Customer>>(HttpStatusCode.OK, custs);
        }

        // GET api/customers/5
        public HttpResponseMessage Get(int id)
        {
            var cust = _Repository.GetCustomer(id);
            if (cust == null) throw new HttpResponseException(HttpStatusCode.NotFound);
            return Request.CreateResponse<Customer>(HttpStatusCode.OK, cust);
        }

        // POST api/customers
        public HttpResponseMessage Post([FromBody]Customer cust)
        {
            var newCust = _Repository.InsertCustomer(cust);
            if (newCust != null)
            {
                var msg = new HttpResponseMessage(HttpStatusCode.Created);
                msg.Headers.Location = new Uri(Request.RequestUri + newCust.ID.ToString());
                return msg;
            }
            throw new HttpResponseException(HttpStatusCode.Conflict);
        }

        // PUT api/customers/5
        public HttpResponseMessage Put(int id, [FromBody]Customer cust)
        {
            var status = _Repository.UpdateCustomer(cust);
            if (status) return new HttpResponseMessage(HttpStatusCode.OK);
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        // DELETE api/customers/5
        public HttpResponseMessage Delete(int id)
        {
            var status = _Repository.DeleteCustomer(id);
            if (status) return new HttpResponseMessage(HttpStatusCode.OK);
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        [HttpGet]
        public List<Order> Orders(int custID)
        {
            var orders = _Repository.GetOrders(custID);
            if (orders == null) 
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
            return orders;
        }
    }

Looking through the code you can see that all of the main players are there including methods to handle GET, PUT, POST, and DELETE operations. Each method relies on a repository object behind the scenes to handle interacting with the actual data source. There are a lot of articles out there on using the ASP.NET Web API to build services so I won’t go into more detail here. Check out the Web API site for additional information.

Now that you’ve seen the service let’s switch to the client-side and talk about how AngularJS can be used to consume the service.

Creating an AngularJS Module

AngularJS provides several different options for encapsulating data functionality including services, factories, and providers. In this example I’ll discuss factories (my personal favorite) and another object built-into the framework named $http. Before jumping into the factory code, let’s take a quick look at the module that’s configured for the application that I’ll be discussing throughout this post.

var app = angular.module('customersApp', ['ngRoute']);app.config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/', {        controller: 'customersController',
        templateUrl: '/app/views/customers.html'    })
    .otherwise({ redirectTo: '/' });}]);

In this example a customersApp module is created that acts as a container for other components used in the application such as the factory that will be discussed next. Notice that in addition to defining the cutomersApp module, the code also references the ngRoute module which handles routing. This module is found in a file named angular-route.js which is new in AngularJS 1.2 – see my previous post for additional information.

Once the module is defined it’s used to configure routing for the application. This is done by calling the module’s config() function which accepts a $routeProvider object. In this simple example a single route is defined to handle the root path but additional routes can certainly be defined using the $routeProvider.when() function.

Now that the module is created it’s time to take a look at how a factory can be created and used to call the ASP.NET Web API service.

Creating a Factory

Although AngularJS controllers can call directly into back-end services, I prefer to put that type of functionality into factories so that it’s more re-useable and easier to maintain. The application discussed here relies on a factory named dataFactory to make calls into the ASP.NET Web API service.

If you’re new to factories they can be created by calling the angular.module(‘YourModule"’).factory() function (note that modules can also create services, providers, values, and constants):

m1

A factory is responsible for creating and returning an object that can be used to work with data, validate business rules, or perform a variety of other tasks. Angular factories are singletons by default so the object returned by a factory is re-used by the application.

Here’s an example of a factory that handles GET, PUT, POST, and DELETE calls to the ASP.NET Web API service. The factory creates an object that handles making calls to the server.

angular.module('customersApp')
    .factory('dataFactory', ['$http', function($http) {

    var urlBase = '/api/customers';
    var dataFactory = {};

    dataFactory.getCustomers = function () {
        return $http.get(urlBase);
    };

    dataFactory.getCustomer = function (id) {
        return $http.get(urlBase + '/' + id);
    };

    dataFactory.insertCustomer = function (cust) {
        return $http.post(urlBase, cust);
    };

    dataFactory.updateCustomer = function (cust) {
        return $http.put(urlBase + '/' + cust.ID, cust)
    };

    dataFactory.deleteCustomer = function (id) {
        return $http.delete(urlBase + '/' + id);
    };

    dataFactory.getOrders = function (id) {
        return $http.get(urlBase + '/' + id + '/orders');
    };

    return dataFactory;
}]);

The $http object is injected into the factory at runtime by AngularJS and used to make Ajax calls to the server. Looking through the code you can see that it exposes get(), post(), put(), and delete() functions that make it a piece of cake to work with RESTful services. Because the functions defined in the factory don’t know what to do with the data they handle, each one returns a promise that can be wired up to callback functions by the caller.

As mentioned earlier, factories are singletons by default so the object returned by the factory can be re-used over and over by different controllers in the application. While AngularJS services can also be used to perform this type of functionality, a service returns an instance of itself (it’s also a singleton) and uses the “this” keyword as a result. Factories on the other hand are free to create their own objects inside of the factory function and return them. To make this more clear let’s compare the factory shown earlier to a service.

Here’s an example of a service named dataService. Note that the function defined in the service is the object returned to the caller rather than an object defined inside of the function as with a factory.

angular.module('customersApp')
    .service('dataService', ['$http', function ($http) {

        var urlBase = '/api/customers';

        this.getCustomers = function () {
            return $http.get(urlBase);
        };

        this.getCustomer = function (id) {
            return $http.get(urlBase + '/' + id);
        };

        this.insertCustomer = function (cust) {
            return $http.post(urlBase, cust);
        };

        this.updateCustomer = function (cust) {
            return $http.put(urlBase + '/' + cust.ID, cust)
        };

        this.deleteCustomer = function (id) {
            return $http.delete(urlBase + '/' + id);
        };

        this.getOrders = function (id) {
            return $http.get(urlBase + '/' + id + '/orders');
        };
    }]);

Creating the Controller

Now that the factory is created a controller can use it to make calls to the ASP.NET Web API service. Here’s an example of a controller named customersController that relies on the dataFactory for data retrieval and manipulation. All of the dataFactory functions return a promise which is resolved by the controller using the success() and error() functions. Once data is returned from the factory (assuming success) the $scope is updated which will drive the user interface.

angular.module('customersApp')
    .controller('customersController', ['$scope', 'dataFactory', 
        function ($scope, dataFactory) {

    $scope.status;
    $scope.customers;
    $scope.orders;

    getCustomers();

    function getCustomers() {
        dataFactory.getCustomers()
            .success(function (custs) {
                $scope.customers = custs;
            })
            .error(function (error) {
                $scope.status = 'Unable to load customer data: ' + error.message;
            });
    }

    $scope.updateCustomer = function (id) {
        var cust;
        for (var i = 0; i < $scope.customers.length; i++) {
            var currCust = $scope.customers[i];
            if (currCust.ID === id) {
                cust = currCust;
                break;
            }
        }

        dataFactory.updateCustomer(cust)
          .success(function () {
              $scope.status = 'Updated Customer! Refreshing customer list.';
          })
          .error(function (error) {
              $scope.status = 'Unable to update customer: ' + error.message;
          });
    };

    $scope.insertCustomer = function () {
        //Fake customer data
        var cust = {
            ID: 10,
            FirstName: 'JoJo',
            LastName: 'Pikidily'
        };
        dataFactory.insertCustomer(cust)
            .success(function () {
                $scope.status = 'Inserted Customer! Refreshing customer list.';
                $scope.customers.push(cust);
            }).
            error(function(error) {
                $scope.status = 'Unable to insert customer: ' + error.message;
            });
    };

    $scope.deleteCustomer = function (id) {
        dataFactory.deleteCustomer(id)
        .success(function () {
            $scope.status = 'Deleted Customer! Refreshing customer list.';
            for (var i = 0; i < $scope.customers.length; i++) {
                var cust = $scope.customers[i];
                if (cust.ID === id) {
                    $scope.customers.splice(i, 1);
                    break;
                }
            }
            $scope.orders = null;
        })
        .error(function (error) {
            $scope.status = 'Unable to delete customer: ' + error.message;
        });
    };

    $scope.getCustomerOrders = function (id) {
        dataFactory.getOrders(id)
        .success(function (orders) {
            $scope.status = 'Retrieved orders!';
            $scope.orders = orders;
        })
        .error(function (error) {
            $scope.status = 'Error retrieving customers! ' + error.message;
        });
    };
}]);
Summary

AngularJS has all of the building block components needed to integrate with back-end services. In this post you’ve seen how the $http object can be used to call a RESTful ASP.NET Web API service without writing a lot of code. You’ve also seen how data functionality can be encapsulated into a factory (or service) so that it can be re-used throughout an application. Keep in mind that although I used ASP.NET Web API in this example, the same general AngularJS code could be used to call a different back-end service created using Node.js, Python, or a variety of other languages/frameworks. Although the sample shown here is quite simple, I hope it gets you started using AngularJS and back-end services.







Check out my other posts on AngularJS


Topic: Development

Sign in with

Or register