In this article, I will discuss how AngularJS supports working with RESTful web services.
Representational State Transfer (REST) is a style of API that operates over HTTP requests. The requested URL identifies the data to be operated on, and the HTTP method identifies the operation that is to be performed. REST is a style of API rather than a formal specification, and there is a lot of debate and disagreement about what is and isn’t RESTful, a term used to indicate an API that follows the REST style. AngularJS is pretty flexible about how RESTful web services are consumed. You should use the services that I describe in this article when you are performing data operations on a RESTful API. You may initially prefer to use the $http service to make Ajax requests, especially if you are coming from a jQuery background. To that end, I describe the use of $http at the start of the article, before explaining its limitations when used with REST and the advantages of using the $resource service as an alternative. For this, we first need to create a RESTful web API.
REST Services
REST (REpresentational State Transfer) services allow for a “separation of concerns.”REST services are not concerned with the user interface or user state, and clients that use REST services are not concerned with data storage or business logic. Clients can be developed independently of the REST services, as we have shown in previous chapters, using mock data. REST services can likewise be developed independently of the client, with no concern for client specifics or even the types of clients using the services. REST services should perform in the same way for all clients. REST services should be stateless. A REST service should never hold data in a session
variable. All information needed for a REST service call should be contained in the request and header passed from the client to the service. Any state should be held in the client and not in the service. There are many ways to hold state in an AngularJS application, including local storage, cookies, or cache storage. A REST web service is said to be RESTful when it adheres to the following constrants:
It’s URL-based (e.g., http://www.micbutton.com/rs/blogPost).
It uses an Internet media type such as JSON for data interchange.
It uses standard HTTP methods (GET, PUT, POST, DELETE).
Name | Type | Required |
name | String | Yes |
category | string | yes |
price | number | Yes |
Now we need to write down a REST Api with the post, get and delete method.
Now, come back to angular js and add the below mentioned html and js files for the program -
http_restapi.html
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" ng-app="TestApp">
- <head>
- <title>Access REST Api using $http</title>
- <script src="angular.js"></script>
- <link href="bootstrap.css" rel="stylesheet" />
- <link href="bootstrap-theme.css" rel="stylesheet" />
- <script src="http_restapi.js"></script>
- </head>
- <body ng-controller="mainController">
- <div class="panel panel-primary">
- <h3 class="panel-heading">Products</h3>
- <ng-include src="'tableView.html'" ng-show="displayMode == 'list'"></ng-include>
- <ng-include src="'editorView.html'" ng-show="displayMode == 'edit'"></ng-include>
- </div>
- </body>
- </html>
tableview.html
- <div class="panel-body">
- <table class="table table-striped table-bordered">
- <thead>
- <tr>
- <th>Name</th>
- <th>Category</th>
- <th class="text-right">Price</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="item in products">
- <td>{{item.name}}</td>
- <td>{{item.category}}</td>
- <td class="text-right">{{item.price | currency}}</td>
- <td class="text-center">
- <button class="btn btn-xs btn-primary"
- ng-click="deleteProduct(item)">
- Delete
- </button>
- <button class="btn btn-xs btn-primary" ng-click="editOrCreateProduct(item)">
- Edit
- </button>
- </td>
- </tr>
- </tbody>
- </table>
- <div>
- <button class="btn btn-primary" ng-click="listProducts()">Refresh</button>
- <button class="btn btn-primary" ng-click="editOrCreateProduct()">New</button>
- </div>
- </div>
editorview.html
- <div class="panel-body">
- <div class="form-group">
- <label>Name:</label>
- <input class="form-control" ng-model="currentProduct.name" />
- </div>
- <div class="form-group">
- <label>Category:</label>
- <input class="form-control" ng-model="currentProduct.category" />
- </div>
- <div class="form-group">
- <label>Price:</label>
- <input class="form-control" ng-model="currentProduct.price" />
- </div>
- <button class="btn btn-primary" ng-click="saveEdit(currentProduct)">Save</button>
- <button class="btn btn-primary" ng-click="cancelEdit()">Cancel</button>
- </div>
app.js
- var testApp = angular.module('TestApp', []);
http_restapi.js
- testApp.controller("mainController", function ($scope) {
- $scope.displayMode = "list";
-
- $scope.currentProduct = null;
-
- $scope.listProducts = function () {
- $scope.products = [
- { id: 0, name: "Dummy1", category: "Test", price: 1.25 },
- { id: 1, name: "Dummy2", category: "Test", price: 2.45 },
- { id: 2, name: "Dummy3", category: "Test", price: 4.25 }];
- }
-
- $scope.deleteProduct = function (product) {
- $scope.products.splice($scope.products.indexOf(product), 1);
- }
-
- $scope.createProduct = function (product) {
- $scope.products.push(product);
- $scope.displayMode = "list";
- }
-
- $scope.updateProduct = function (product) {
- for (var i = 0; i < $scope.products.length; i++) {
- if ($scope.products[i].id == product.id) {
- $scope.products[i] = product;
- break;
- }
- }
- $scope.displayMode = "list";
- }
-
- $scope.editOrCreateProduct = function (product) {
- $scope.currentProduct = product ? angular.copy(product) : {};
- $scope.displayMode = "edit";
- }
-
- $scope.saveEdit = function (product) {
- if (angular.isDefined(product.id)) {
- $scope.updateProduct(product);
- } else {
- $scope.createProduct(product);
- }
- }
-
- $scope.cancelEdit = function () {
- $scope.currentProduct = {};
- $scope.displayMode = "list";
- }
- $scope.listProducts();
- });
Now, in the above angular js controller file at first I will not use $http service. Now run the program and the output will be as below -
Now, I will change the crud related method with $http service method to access the restapi data.
- testApp
- .constant("baseUrl", "http://localhost:1021/products/")
- .controller("mainController", function ($scope, $http, baseUrl) {
- $scope.displayMode = "list";
-
- $scope.currentProduct = null;
-
- $scope.listProducts = function () {
- $http.get(baseUrl).success(function (data) {
- $scope.products = data;
- });
- }
-
- $scope.deleteProduct = function (product) {
- $http({
- method: "DELETE",
- url: baseUrl + product.id
- }).success(function () {
- $scope.products.splice($scope.products.indexOf(product), 1);
- });
- }
-
- $scope.createProduct = function (product) {
- $http.post(baseUrl, product).success(function (newProduct) {
- $scope.products.push(newProduct);
- $scope.displayMode = "list";
- });
- }
-
- $scope.updateProduct = function (product) {
- $http({
- url: baseUrl + product.id,
- method: "PUT",
- data: product
- }).success(function (modifiedProduct) {
- for (var i = 0; i < $scope.products.length; i++) {
- if ($scope.products[i].id == modifiedProduct.id) {
- $scope.products[i] = modifiedProduct;
- break;
- }
- }
- $scope.displayMode = "list";
- });
- }
-
- $scope.editOrCreateProduct = function (product) {
- $scope.currentProduct = product ? angular.copy(product) : {};
- $scope.displayMode = "edit";
- }
-
- $scope.saveEdit = function (product) {
- if (angular.isDefined(product.id)) {
- $scope.updateProduct(product);
- } else {
- $scope.createProduct(product);
- }
- }
-
- $scope.cancelEdit = function () {
- $scope.currentProduct = {};
- $scope.displayMode = "list";
- }
- $scope.listProducts();
- });
Installing the ngResource Module
The $resource service is defined within an optional module called ngResource that must be downloaded into the angularjs folder. Go to http://angularjs.org, click Download, select the version you want and download the file. Download the angular-resource.js file into the angularjs folder.
Configuring $resource Service
The first thing I have to do is set up the $resource service so that it knows how to work with the RESTful Deployd service. Here is the statement that does this:
- $scope.productsResource = $resource(baseUrl + ":id", { id: "@id" });
The $resource service object is a function that is used to describe the URLs that are used to consume the RESTful service. The URL segments that change per object are prefixed with a colon (the : character). For the first argument I combine the value of the baseUrl constant with :id to indicate a URL segment that will change. The second argument is a configuration object whose properties specify where the value for the variable segment will come from. Each property must correspond to a variable segment from the first argument, and the value can be fixed or, as I have done in this example, bound to a property on the data object by prefixing a property name with the @ character. The result from calling the $resource service function is an access object that can be used to query and modify the server data using the methods that I have described in below.
Modifying Data Objects
The query method populates the collection array with Resource objects, which defines all of the properties specified in the data returned by the server and some methods that allow manipulation of the data without needing to use the collections array. The below tables describe the methods that Resource objects define.
Name | Descriptions |
$delete() | Deletes the object from the server; equivalent to calling $remove() |
$get() | Refreshes the object from the server, clearing any uncommitted local changes |
$remove() | Deletes the object from the server; equivalent to calling $delete() |
$save() | $save() |
Deleting Data Objects
The $delete and $remove methods generate the same requests to the server and are identical in every way. The wrinkle in their use is that they send the request to remove an object from the server but don’t remove the object from the collection array. This is a sensible approach, since the outcome of the request to the server isn’t known until the response is received and the application will be out of sync with the server if the local copy is deleted and the request subsequently returns an error.
Configuring the $resource Service Actions
The get, save, query, remove, and delete methods that are available on the collection array and the $-prefixed equivalents on individual Resource objects are known as actions. By default, the $resource service defines the actions. The $resource service object function can be invoked with a third argument that defines actions. The actions are expressed as object properties whose names correspond to the action that is being defined, or redefined, since you can replace the default actions if need be. Each action property is set to a configuration object. I have used only one property, method, which sets the HTTP method used for the action. The effect of my change is that I have defined a new action called create, which uses the POST method, and I have redefined the save action so that it uses the PUT method. The result is to make the actions supported by the productsResoures access object more consistent with the Deployd API, separating the requests for creating new objects from those that modify existing objects.
Name | Descriptions |
method | Sets the HTTP method that will be used for the Ajax request. |
params | Specifies values for the segment variables in the URL passed as the first argument to the $resource service function. |
url | Overrides the default URL for this action. |
isArray | When true, specifies that the response will be a JSON data array. The default value, false, specifies that the response to the request will be, at most, one object. |
Now I again the change the angular js controller file for implement the $resource objects.
- testApp
- .constant("baseUrl", "http://localhost:1021/products/")
- .controller("mainController", function ($scope, $http, $resource, baseUrl) {
- $scope.displayMode = "list";
-
- $scope.currentProduct = null;
-
- $scope.productsResource = $resource(baseUrl + ":id", { id: "@id" });
-
- $scope.listProducts = function () {
- $scope.products = $scope.productsResource.query();
- }
-
- $scope.deleteProduct = function (product) {
- product.$delete().then(function () {
- $scope.products.splice($scope.products.indexOf(product), 1);
- });
- $scope.displayMode = "list";
- }
-
- $scope.createProduct = function (product) {
- new $scope.productsResource(product).$save().then(function (newProduct) {
- $scope.products.push(newProduct);
- $scope.displayMode = "list";
- });
- }
-
- $scope.updateProduct = function (product) {
- product.$save();
- $scope.displayMode = "list";
- }
-
- $scope.editOrCreateProduct = function (product) {
- $scope.currentProduct = product ? product : {};
- $scope.displayMode = "edit";
- }
-
- $scope.saveEdit = function (product) {
- if (angular.isDefined(product.id)) {
- $scope.updateProduct(product);
- } else {
- $scope.createProduct(product);
- }
- }
-
- $scope.cancelEdit = function () {
- if ($scope.currentProduct && $scope.currentProduct.$get) {
- $scope.currentProduct.$get();
- }
- $scope.currentProduct = {};
- $scope.displayMode = "list";
- }
- $scope.listProducts();
- });