Introduction
This is important. At least for me. When I first learned AngularJS, there are a couple of things I found that were very difficult. Here they are:
- The idea of dependency injections that link all the modules and components into one application
- Filtering, and how to create customized filter
- Directives, there are many ways of creating them. And the concept of isolated scope. It is a bit hard to understand how both actually worked.
- ngIf and the child scope it created
- And many miscellaneous things that break the application in mysterious ways
The main problem is the syntax of AngularJS. It is very different from conventional languages, like Java and C#. Once I understand how to bypass these issues, it is like removing a very thin veil off this technology and everything becomes clear. That is when I was able to use this technology with ease. The key to this is reading the documentation, and reading other people's tutorial. Then do a lot of experiments, learning the syntax. Eventually, reading AngularJS code becomes easy and everything becomes easy too. This tutorial is a summary of how I use the ngResource
component.
In an AngularJS application, any data exchange between the app and the backend web service is handled with RESTFul API calls. That is, the exchange is handled with HTTP Get
, HTTP Post
, HTTP Put
and HTTP Delete
requests. Request can package input parameters in the URL path, as request parameters, JSON, or x-www-url-encoded request string
. This tutorial will explore all these. What is not being covered in this is form based multipart file upload. File upload will be discussed in a different tutorial. Before we get into the actual discussion, I would like to go over the sample project's architecture.
Sample Project's Overall Architecture
The sample project included in this tutorual is a Spring Boot based application. The Java code will create a RESTFul application. Inside the resources folder of this application, there is an index.html page under the sub folder called "static", which hosts the AngularJS application that exercises all the scenarios that I am interested in discussing.
The AngularJS application is composed with two files, one is called "app.js", which deals with the user interaction on the page "index.html"; the other file is the "sampleService.js". This second JS file will provide the services functionality of calling the RESTFul service.
The "index.html" page is divided into eight sections. Every section has two buttons, one is called "Test
". When clicked, the method that handles the click will call the service object (of "sampleService.js"), which in turn calls ngResource
to fetch the data from RESTFul API. Once the data returns and successfully handled, the page will display the API URL of the call and the response data in JSON format. The other button "Reset" will clean up all the variables and clear up the section. Then you can run the scenario again.
Here is a screenshot of the test section I just described:
Here is a screenshot of the test section after the button "Test" is clicked:
A Basic Use of ngResource
In this section, I like to show you how to use ngResource
. The first step is to inject the module ngResource
into application module or into any module that will need ngResource
. Here is the code that does it:
"use strict";
var mod = angular.module("restSampleApp", [ "ngResource", "sampleServiceModule" ]);
Here is another example:
"use strict";
var mod = angular.module("sampleServiceModule", [ "ngResource" ]);
In both examples, I create an AngularJS module, which is dependant on other available modules. The method module
in angular
takes the first parameter as the name of the newly created module, the second parameter is an array of the names of depended modules. ngResource
is the module of the AngularJS resource service that can be used to fetch data from back end RESTFul API. The second code snippet is a great example, the newly created module has just one dependency, which is ngResource
.
The next step is to inject the object called $resource
into the controller/factory/etc. Here is how:
mod.controller("restSampleController", [ "$scope", "$resource", "sampleRestService",
function ($scope, $resource, sampleRestService) {
...
}
]);
Previously, an object called mod
has been created via angular.module(...)
. Using this object, we can define a controller or something else, like a factory which creates a service
object. In the above code snippet, we are creating a controller. The first parameter is the name of the controller. The next parameter is an array where the first couple elements are name of the objects that the controller depended on. The controller itself is a function which takes all these objects as parameters. In the above code snippet, the controller injects three different objects: "$scope"
, "$resource"
, "sampleRestService"
. And in the function which the controller actually depended on, uses the name of the objects directly as parameters. This is one of the concepts one must grasp in order to comfortably utilize AngularJS.
Anyways, one of the injected object is called $resource
. Because it is part of AngularJS framework, it has "$
" prefix. I intentionally injected ngResource
object to this main controller so that I can demo the basic use of ngResource
.
Now, it is time to see the most basic use of ngResource
. In "app.js", there is a scope bound method called vm.clickGetCar()
. Inside, the code logic shows the basic use. Here is the code:
vm.clickGetCar = function () {
var restSvc = $resource("./getCar/:make/:year/:model", {
make: "@make",
year: "@year",
model: "@model"
}, {
"getCar": {
method: "get",
isArray: false
}
});
var prom = restSvc.getCar({
make: "honda",
year: 1997,
model: "civic"
}).$promise;
prom.then(function (result) {
if (result) {
vm.getCarSucceeded = true;
vm.getCarResponse = JSON.stringify(result, null, 3);
} else {
vm.getCarFailed = true;
vm.getCarErrorMsg = "Error occurred.";
}
}, function(error) {
vm.getCarFailed = true;
vm.getCarErrorMsg = "Error occurred.";
});
};
This method is divided in three separated portions. The first portion defines a ngResource
object by calling $resource
as a method. $resource
method has three input parameters:
- The first parameter is a
string
representing the URL pattern of the REST API. In this case, it is the relative path and it has three parameters in the path, prefixed by ":
". For example, the variable name ":make
" means this part of the path is the value of the maker of the car. The other two are the year of the car and model name of the car. - The second parameter is an object with three properties. It is used to define how input values can be converted as values for the URL path. When
ngResource
object is used to invoke the back end service, the input values such as the maker, the model and the year of the car will be passed in. The values will be part of the URL to be sent as HTTP request. - The last parameter is another object that defines an action for this
ngResource
. It defines how the HTTP request can be sent. In this case, the HTTP request shall be sent as a GET
request. When the response returns, ngResource
object expects the object is just an object, not an array of objects. The inner object is associated with a key called "getCar
", "getCar
" will become an method. You will see it in action soon.
This is the first portion:
var restSvc = $resource("./getCar/:make/:year/:model", {
make: "@make",
year: "@year",
model: "@model"
}, {
"getCar": {
method: "get",
isArray: false
}
});
The second portion is invoking the ngResource
object by calling the action method defined -- getCar()
. The invocation will return a promise. This portion is closely related with the first portion. The first portion uses $resource()
and dynamically creates an object with at least one method in it called getCar()
. The second portion uses the ngResource
object and invokes its method called getCar()
. Because the invocation is async
, we need to supply a callback method to it so that when the invocation completes, it uses the customized callback method to handle the results. This happens in the third portion. Here is the second portion:
var prom = restSvc.getCar({
make: "honda",
year: 1997,
model: "civic"
}).$promise;
Once we get a promise out of the async invocation, we can supply a callback to handle the returned result. The promise object has a method called then()
. It takes two functions as input. The first handles the scenario when a success response has returned. The second one handles error response. Here is the third portion:
prom.then(function (result) {
if (result) {
vm.getCarSucceeded = true;
vm.getCarResponse = JSON.stringify(result, null, 3);
} else {
vm.getCarFailed = true;
vm.getCarErrorMsg = "Error occurred.";
}
}, function(error) {
vm.getCarFailed = true;
vm.getCarErrorMsg = "Error occurred.";
});
As seen, if the response is successful, the returned result will be a JavaScript object. It is either object or an array. In this case, we are expecting just an object. If the object is successfully captured, it will be converted as a JSON string. The scope bound variables vm.getCarSucceeded
and vm.getCarResponse
will store the results. Then the page will display the response object.
Here is how the HTML page display the results:
<div class="row">
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
Rest Get
</div>
<div class="panel-body">
<p>Get HTTP Request with Path Variables. Use ngResource directly.</p>
<div ng-if="vm.getCarSucceeded">
<p><strong>Request Url</strong></p>
<pre>
{{vm.getCarRequestUrl}}
</pre>
<p><strong>Response</strong></p>
<pre>
{{vm.getCarResponse}}
</pre>
</div>
<div class="row" ng-if="vm.getCarFailed">
<div class="col-xs-12">
<div class="alert alert-danger">{{vm.getCarErrorMsg}}</div>
</div>
</div>
<button class="btn btn-primary" ng-click="vm.clickGetCar()">Test</button>
<button class="btn btn-primary" ng-click="vm.resetGetCar()">Reset</button>
</div>
</div>
</div>
</div>
In the above HTML code, the ng-if
for the display part uses vm.getCarSucceeded
to control whether it displays the response object or not. And if vm.getCarSucceeded
is set to true
, it will show the URL of the request and the JSON response. If the request handling failed somehow, the div
with ng-if
that uses vm.getCarFailed
to control whether it will display or not. It will display error message.
This is it. As simple and as complex as it is, this is how ngResource
can be used to handle front end and back end data exchange. In the next section, I will show you a different way of using ngResource
.
One ngResource Object with Many Actions
In the previous section, I have shown you the way of using ngResource
to define one object with just one request URL, with just one action. I like to use ngResource
in a different way. Instead of specifying the URL and input parameters with the first and second parameters of $resource()
, I would set them to null
and rely on the actions as individual request handlers. Take a look at sampleService.js. The $resource()
method call has many actions, and the first and second parameters are default to null
.
var restSvc = $resource(null, null, {
"getCar": {
url: "./getCar/:make/:year/:model",
method: "get",
isArray: false,
params: {
make: "@make",
year: "@year",
model: "@model"
}
},
...
});
The code snippet shown here does the same thing as in the previous section. The difference is that the request URL, and the input parameters are all part of the action definition. The action name is the same as the one from previous section, called "getCar
". This action also does the same thing, it takes the maker, the model and the year of the car, and fetches the car information.
Here is how this ngResource
object can be used. In the same service definition, I created this service method called getCar()
:
svc.getCar = function (make, year, model) {
return restSvc.getCar({
make: make,
year: year,
model: model
}).$promise;
};
This service method is doing the same thing as shown in the previous section. It calls the ngResource
object, and invokes the action called getCar()
. Then return the promise.
In "app.js", you can see how the service
method is used:
vm.clickGetCar2 = function () {
sampleRestService.getCar("honda", 1997, "civic").then(function (result) {
if (result) {
vm.getCar2Succeeded = true;
vm.getCar2Response = JSON.stringify(result, null, 3);
} else {
vm.getCar2Failed = true;
vm.getCar2ErrorMsg = "Error occurred.";
}
}, function(error) {
vm.getCar2Failed = true;
vm.getCar2ErrorMsg = "Error occurred.";
});
};
As you can see from this example, the use of the ngResource
can also be divided into three parts:
- The first is using
ngResource
$resource
to define an ngResource
object with action that can communicate with the back end web service. - Use the
ngResrouce
object to perform the data exchange with the back end, then return the promise object. - Use the promise to handle the actual data, and possibly error returned from the back end service.
At this point, we have seen the way ngResource
fetches and handles the data. I can tell you, aside from some subtle differences, the three portions all look the same. In the next section, I will be discussing these subtle differences.
Handling HTTP Requests in Various Ways
Now that we know how to utilize one ngResource
object with many actions, we can discuss how to use ngResource
object to handle the most common HTTP Requests. In the following subsections, we will see how to use ngResource
object to handle requests of HTTP Get
, HTTP Post
, HTTP Put
and HTTP Delete
action types.
Handling HTTP Get Requests
In the previous example, we have seen the way of ngResource
handles HTTP GET
request where the input parameters are part of the URL path. For example, the URL that was used in the previous example is:
http://localhost:8080/getCar/honda/1997/civic
As shown, ngResource
defines the action as the following:
var restSvc = $resource(null, null, {
"getCar": {
url: "./getCar/:make/:year/:model",
method: "get",
isArray: false,
params: {
make: "@make",
year: "@year",
model: "@model"
}
},
...
});
The definition is shown as:
- The property
url
has the value "./getCar/:make/:year/:model
". The input variables are defined with prefix of ":
". - Then there is the property
params
. The definition above indicates that params
expects an object. The object's properties are mapped by name. What I do is keep the names as same as the object
property names.
When the action is used, it is done like this:
svc.getCar = function (make, year, model) {
return restSvc.getCar({
make: make,
year: year,
model: model
}).$promise;
};
The ngResource
object is called "restSvc
", and object method getCar()
takes an object that has three properties: "make
", "year
", and "model
". The object will be considered as the value for params
defined in the action of "restSvc
".
In a different scenario, assuming the HTTP request is still a HTTP GET
request. Instead of having input parameter in the URL path, they are request parameters. The URL path looks like this:
http://localhost:8080/getCar?make=honda&year=1997&model=civic
Use ngResource
to handle such URL, the definition of the ngReosurce
looks similar to the previous one, here it is:
var restSvc = $resource(null, null, {
...
"getCarReq": {
url: "./getCarReq",
method: "get",
isArray: false,
params: {
make: "@make",
year: "@year",
model: "@model"
}
},
...
});
To use this ngReosurce
action via the ngResource
object (in this example called "restSvc
"), it is the same as shown in the previous example. Here is the code, check it out yourself:
svc.getCarReq = function (make, year, model) {
return restSvc.getCarReq({
make: make,
year: year,
model: model
}).$promise;
};
What is different is that in the previous example, the URL has the input variables which can be mapped from the params
to the path
variables. In this example, the values in params
is by default mapped as request parameters.
These two examples return only a single object from the server side. What about returning a list of objects. It is actually pretty easy to do. In the sample program, I have a URL like this:
http://localhost:8080/getAllCars?maker=honda&model=civic
The request parameters are dummy value, and the URL guarantees to return a list of car info as a JSON array. In order to handle this at the client end, I have defined the ngReosurce
action as the following:
var restSvc = $resource(null, null, {
...
"getAllCars": {
url: "./getAllCars",
method: "get",
isArray: true,
params: {
maker: "@maker",
model: "@model"
}
}
});
The only thing that is different is the value for isArray
, which is set to true
. This is very important, regardless of what type of HTTP method you are using, whenever you expect a request to return a single object, the isArray
property should be set to false
. And if it returns an array of objects, the isArray
property should be set to true
. If you are not careful with this property, you will get a runtime exception.
Handling HTTP Post Requests
These are all the easy stuff. Let's take a look at a more complex scenario. Assuming we have a URL and the only way to interact with it is using HTTP POST
. The input object has to be in JSON format. Here is the URL:
http://localhost:8080/addCar
To handle a scenario like this, the ngResource
action definition is a little different:
- The URL of this new post operation will be different. Unless you are using the same URL and different HTTP methods to distinguish them, the URLs for actions should always be different.
- The method should be "
post
" instead of "get
". - Instead of using property "
params
" to specify how input data mapped to the request, we have to use a different property: "data
".
Here is the definition of the ngResource
action for this HTTP Post
example:
var restSvc = $resource(null, null, {
...
"addCar": {
url: "./addCar",
method: "post",
isArray: false,
data: "@addCar"
},
...
});
The values mapping for the "data
" is a bit different. Instead of specifying an object, I assigned a string
to it. The string
value "@addCar
" means when the action (that is, "addCar
") is called as a method of the ngReosurce
object, the passed in parameter by the name of "addCar
" will be assigned as the data to the request. And by default, the data property is converted as a JSON string
when passed to the backend web service.
Here is the code that uses this ngResource
action:
svc.addCar = function (addCarReq) {
return restSvc.addCar(addCarReq).$promise;
};
The service
method takes in a parameter called addCarReq
, which will be passed to ngResource
object restSvc
's addCar
action. This will be the value of the "addCar
" parameter. How the service object is used for this action is pretty much the same as before:
vm.clickAddCar = function () {
var addCarReq = {
maker: "toyota",
model: "camry",
yearOfManufacturing: 1997,
suggestedRetailPrice: 21800,
fullPrice: 22395
};
sampleRestService.addCar(addCarReq).then(function (result) {
if (result) {
vm.addCarSucceeded = true;
vm.addCarResponse = JSON.stringify(result, null, 3);
} else {
vm.addCarFailed = true;
vm.addCarErrorMsg = "Error occurred.";
}
}, function(error) {
vm.addCarFailed = true;
vm.addCarErrorMsg = "Error occurred.";
});
};
This is just one way of passing data to the back end web service. In the old days, when a web developer working with HTML and use form submit, it uses content type of "application/x-www-form-urlencoded
". This is a very convenient way of communicating with the back end web service when you have to deal with a very small set of data. For example, you have a request that you want to activate or deactivate a car
model. All you need is the ID of the car model and a boolean value. Do you need to wrap them as an object and passed to the back end web service? No. you can define an action method that takes the two input data values as parameters. Here is the Spring REST action method for my example, this is Java FYI:
@RequestMapping(value="/carForPurchase",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<GenericResponse> carForPurchase(
@RequestParam("carId")
String carId,
@RequestParam("readyToPurchase")
boolean readyToPurchase
)
{
System.out.println("car Id: " + carId);
System.out.println("ready To Purchase: " + readyToPurchase);
GenericResponse resp = new GenericResponse();
if (carId == null || carId.equals(""))
{
resp.setId("");
resp.setSuccess(false);
resp.setDetailMessage("No record updated. Invalid input.");
}
else
{
resp.setId(carId);
resp.setSuccess(true);
resp.setDetailMessage("Car is now available for purchase.");
}
return ResponseEntity.ok(resp);
}
As you can see, the method input parameters are primitive objects of types String
and Boolean
. The nicest thing about this is that I don't need to defined an object type just to wrap two properties. But, it becomes slightly more complicated on the JavaScript end. In order to support this scenario, on the AngularJS side, we need to do a couple of new things:
- For the
ngResource
action, the method should be "post
" instead of "get
". And the URL should be the new URL. - The input data value is still passed in with property "
data
" in the ngResource
action. - We need to override the HTTP header with the new content type.
- Lastly, we need to have a data transformation method that could change the data object into
x-www-urlencoded string
. And we need to dependency inject some new object into the sample service module.
First, we need to add some new dependency injection to the sampler service module. Here it is:
mod.factory("sampleRestService", [ "$resource", "$httpParamSerializer",
function ($resource, $httpParamSerializer) {
...
}
]);
The dependency injection that we need is called $httpParamSerializer
. This is part of the AngularJS core http module. So I don't need to inject the module. And it can transform the actual data object into url encoded string
. To do this, we need a new local method:
function uriEncodedParams(data) {
var retVal = "";
if (data) {
retVal = $httpParamSerializer(data);
}
return retVal;
}
All this method does is to check and see if the data object is not null
or undefined. Then it passes the data object to the method $httpParamSerializer()
, which in turn returns a x-www-urlencoded string
that represents the data
object. The string
would look like this:
carId=ff7c05f0e38a4c70a5bb821081e59efd&readyToPurchase=true
Now, we have enough of resources to define the ngReource
action we need:
var restSvc = $resource(null, null, {
...
"carForPurchase": {
url: "./carForPurchase",
method: "post",
isArray: false,
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
transformRequest: uriEncodedParams
},
...
});
Compared to the previous example of HTTP Post request handling, the only differences are:
- We added a new property called "
headers
". This property would overwrite the HTTP headers of the request. In this case, we are only overwriting the "Content-Type
" header. - The second property we had to add is "
transformRequest
". This is equivalent of the "data
" or "params
" properties. The value of this "transformRequest
" is the method name of uriEncodedParams
. What this does is convert the data object into the format we wish. In this case, we want x-www-urlencoded string
.
See how simple it is. It is done by convention. Whatever data that was passed into this ngResource
action, it will be passed to the local method uriEncodedParams()
, which does the transformation. Then the transformed request data will be added to the request.
Here is the code that calls this angResource
action:
svc.carForPurchase = function (req) {
return restSvc.carForPurchase(req).$promise;
};
And here is the code which the client calls this service
method:
vm.clickCarForPurchase = function () {
var carForPurchaseReq = {
carId: "ff7c05f0e38a4c70a5bb821081e59efd",
readyToPurchase: true
};
sampleRestService.carForPurchase(carForPurchaseReq).then(function (result) {
if (result) {
vm.carForPurchaseSucceeded = true;
vm.carForPurchaseResponse = JSON.stringify(result, null, 3);
} else {
vm.carForPurchaseFailed = true;
vm.carForPurchaseErrorMsg = "Error occurred.";
}
}, function(error) {
vm.carForPurchaseFailed = true;
vm.carForPurchaseErrorMsg = "Error occurred.";
});
};
As seen in the client calling code, there is not much difference from the other one we have seen before. We wrap the request in an object, pass it to the service
method and the service
method calls the ngResource
object's action method. In the end, when the HTTP request is handled asynchronously, the result handling would be essentially the same.
At this point, you have seen the most complicated scenario. It turned out to be pretty simple, as long as you understand how it is done. :)
Handling Requests of Two Other HTTP Method Types
There are two other types of HTTP methods, one is called "Put
". The other is called "Delete
". There are reasons why we have the four HTTP request methods, And they are closely related to databases CRUD operations. Method "Post
" is mostly used to add resources. Method "Get
" is mostly used for data retrieval. Method "Put
" is used for add resource if the resource has not been added previously. If the resource has been added already, then the data in the "Put
" would be used to update the resource. Finally, the method "Delete
" request method would remove the resource.
Because the HTTP Put
request is similar to the HTTP Post request, I will just create a simple example ngResource
action that exercises the HTTP Put
method. Here is the definition of ngResource
action:
var restSvc = $resource(null, null, {
...
"updateCar": {
url: "./updateCar",
method: "put",
isArray: false,
data: "@addCar"
},
...
});
As seen, there is not much difference between this action and the ngResource
action for HTTP Post
request. One important concept that is easy to remember is that anything you can do with HTTP Post
with ngResource
syntax wise, you can do the same with HTTP Put
.
The service
method that calls this ngResource
action looks like this:
svc.updateCar = function (req) {
return restSvc.updateCar(req).$promise;
};
At the client end, the service
method is called and handled in the same way. So I won't post the code here. The last thing I like to show is how HTTP Delete
is handled.
Another important concept to remember is that HTTP Delete
is similar to the HTTP Get
request. Data
have to be either part of the URL path or as request parameters. So anything you can do for HTTP Get
request using AngularJS, you can do the same syntax wise with HTTP Delete
request.
Here is the definition of the ngResource
action that handles HTTP Delete
request:
var restSvc = $resource(null, null, {
...
"deleteCar": {
url: "./deleteCar",
method: "delete",
isArray: false,
params: {
id: "@id",
maker: "@maker",
model: "@model",
year: "@year"
}
},
...
});
In this example, the request
URL accepts input data in request
parameters, not as path variables. I am using "params
" to pass in a series of data values to the back end service.
The service method that calls the ngResource
action looks like this:
svc.deleteCar = function (req) {
return restSvc.deleteCar(req).$promise;
};
If you compare this with the example that handles the HTTP Get
requests, you will see both are similar. It is straight forward.
Here they are, all the examples showing how HTTP Requests can be handled with ngResource
. And they are very straight forward. As long as you can absorb the syntax of AngularJS, or at least copy the way different requests are handled, you can quickly grasp how ngResource
works.
How to Run Sample Project
To run the sample project, first you need to go to the folder /src/main/resources/static/assets and the sub folders, find all the *.sj file. and rename them to *.js.
Once all the *.js files have been restored, use a command line, go to the base folder of the project and run the following command:
mvn clean install
When you run this for the first time, it will take some time and download all the dependencies - the jar files. Then, it will compile and package the binaries into a jar file. In the same base folder, run the following command to start up the application:
java -jar target\hanbo-ngresource-sample-1.0.1.jar
Once the application starts up successfully, you can navigate to the following URL and see the sample application:
http://localhost:8080/index.html
Once you can see above screenshot, you have started the sample application successfully. You can go to each of the section and run the sample by clicking the button called "Test". Reset the section by click the button "Reset".
Summary
If you have reached this point, I guess you have enjoyed this article's informative content. I gotta say the process of writing this article has been fun. In this article, I have discussed the common ways of using ngResource
(or AnularJS $resource
service) to send HTTP requests, receive the responses, and handle the response data. Three examples were created to show how to handle HTTP Get
requests. The examples show how to send HTTP Get
requests using path variables, request parameters, and handle response with array instead of single object. Then I created two examples show how HTTP Post works, one is on how to post request using JSON formatted data. The other one is on how to post data using x-www-urlencoded string
. Finally, I covered the use of HTTP Put
and HTTP Delete
with ngResource
. These examples should cover most of the real world scenarios. I hope you enjoy reading this article. Good luck!
History
- 05/14/2019 - Initial draft