Using an AngularJS Factory to Interact with a RESTful Service


What’s covered in this Post?

  • Creating a RESTful Service
  • Creating an AngularJS Module
  • Creating a Factory
  • Creating a Controller
    f

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.

image


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):


image


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 then() function. Once data is returned from the factory (assuming it works) 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()
            .then(function (response) {
                $scope.customers = response.data;
            }, 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)
          .then(function (response) {
              $scope.status = 'Updated Customer! Refreshing customer list.';
          }, 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)
            .then(function (response) {
                $scope.status = 'Inserted Customer! Refreshing customer list.';
                $scope.customers.push(cust);
            }, function(error) {
                $scope.status = 'Unable to insert customer: ' + error.message;
            });
    };

    $scope.deleteCustomer = function (id) {
        dataFactory.deleteCustomer(id)
        .then(function (response) {
            $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;
        }, function (error) {
            $scope.status = 'Unable to delete customer: ' + error.message;
        });
    };

    $scope.getCustomerOrders = function (id) {
        dataFactory.getOrders(id)
        .then(function (response) {
            $scope.status = 'Retrieved orders!';
            $scope.orders = response.data;
        }, 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.

A project that shows this type of code in action can be found at https://github.com/DanWahlin/CustomerManagerStandard.


Check out my other posts on AngularJS

comments powered by Disqus

16 Comments

  • Why didn't you use the $resource instead of $http? Is $resource no good?

  • Demetrius:

    $resource is definitely another option. If I get a chance I'll convert the code to use $resource just as a comparison.

    Dan

  • Great starting guide. However, resources are exactly what you should use in cases like this.

  • Nice job. Very complete post

  • RESTful applications use HTTP requests to post data. right!!
    Now, Are there any server or client side dependencies for implementing REST in an APIs?

  • Tucaz:

    Thanks for checking out the post. $resource is another way to do this and I may post about that in the near future. However, it's definitely not the preferred or only way - that depends on the dev and the app. There are several fringe cases with $resource that can actually make it challenging in some scenarios.

    If you're interested in some of the fringe stuff you can read more here: https://github.com/mgonto/restangular#differences-with-resource (as a side note I'm not inferring Restangular is the way to go - only played with it at this point - they just had a nice write-up about $resource)).

    Dan

  • Are you going to continue to update https://github.com/DanWahlin/CustomerManager?

  • quinntyne:

    Yes - I plan to do additional updates once I can get back to it. I have some new code already done but need to refactor it a bit more before checking it in.

    Dan

  • fantastic

  • Another terrific article Dan. This is exactly what the docs a missing. Would be interested to know if you intend on pushing the limits of Restangular - from my quick look - looks like it has a lot of potential.
    thanks again

  • this is exactly what i needed :)

  • Great post Dan, and thank you for sharing.

    Based on your post, when deleting/adding resource items we have to manually remove those items from the array in $scope after a successful REST call. I'm wondering if there is a more elegant way of doing this because if we use AngularFire all we have to do is remove the element from the array and the library would take care of any http/websocket calls to the responsible service/server.

    Would you care to jump into how we could implement the Observer pattern for this scenario?

  • Glenn:

    Thanks - glad the post was helpful. As far as the Observer pattern, you can setup a watch on properties and be notified as they change. That way you can simply remove the item from the collection and then handle updating the back end in the watch. Something like this

    $scope.$watch('yourProperty', function (oldVal, newVal) {
    console.log(oldVal, newVal);
    //Act on the change here
    }, true);

    It would definitely be nice to have a factory tied into the watch process to automatically handle changes to a collection. I'll have to think through that a bit more.

    Dan

  • Hey Dan,
    What do you mean with several fringe cases with $resource?
    Could you tell me an example?
    Do you think is to good to use it in a production environment?


  • Check out the link below for some info there. I haven't compared the change doc for 1.2 RC though so some of these issues may potentially be gone now:

    https://github.com/mgonto/restangular#differences-with-resource

    Dan

  • Hello Dan,

    Thanks for your post, I've small question here.

    I've noticed that other tutorials use the code below in the factory using $q.defer()

    var _getMovies = function () {

    var deferred = $q.defer();

    $http.get('/api/v1/movies')
    .then(function (result) {
    //scucess
    angular.copy(result.data, _movies);
    deferred.resolve();

    }, function () {
    //failure

    deferred.reject();

    });

    return deferred.promise;
    };

    Can you elaborate more when we should use $q.defer() because I found your way easier to call WebAPI in factory.

    Regards,
    Taiseer

Comments have been disabled for this content.