Code


<!DOCTYPE html> <html> <head> <script src="angular.js"></script> <script src="angular-resource.js"></script> <script src="script.js"></script> <style> .logTextArea { width: 400px; height: 250px; } </style> </head> <body ng-app="mainModule"> <div ng-controller="mainController"> <h3>1. GET request with parameters</h3> <label>Parameter 1: <input type="text" ng-model="getParam1" /></label><br /> <label>Parameter 2: <input type="text" ng-model="getParam2" /></label><br /> <button ng-click="getWithParams(true)">GET call with SUCCESS</button><br /> <button ng-click="getWithParams(false)">GET call with ERROR</button><br /> <textarea class="logTextArea">{{getWithParamsResult}}</textarea> <br /> <h3>2. Resource constructor-level methods</h3> <button ng-click="getCall()">get call</button> <button ng-click="queryCall()">query call</button> <button ng-click="saveCall()">save call</button> <button ng-click="deleteCall()">delete call</button> <button ng-click="putCall()">put call</button><br /> <br /> <strong>{{callName}}</strong><br /> <span ng-hide="callResult === undefined || callResult.$resolved">Waiting for result...</span><br /> <textarea class="logTextArea">{{(callResult.$resolved ? (callResult | json) : "")}}</textarea> <br /> <h3>3. Resource instance-level methods</h3> <button ng-click="instGetCall()">get call</button> <button ng-click="instSaveCall()">save call</button> <button ng-click="instDeleteCall()">delete call</button> <button ng-click="instPutCall()">put call</button><br /> <br /> <strong>{{instCallName}}</strong><br /> <span ng-hide="instCallResult === undefined || instCallResult.$resolved">Waiting for result...</span><br /> <textarea class="logTextArea">{{(instCallResult.$resolved ? (instCallResult | json) : "")}}</textarea> <br /> </div> </body> </html>
angular.module("mainModule", ["ngResource"]) .factory("PeopleService", function ($resource) { // Construct a resource object that can // interact with the RESTful API of the server. var resource = $resource("people/:operation/:id", { id: 0 }, { // A custom method to update the picture of the person updatePicture: { method: "PUT", isArray: false } } ); // Custom function to retrieve a person by ID resource.retrievePerson = function (personId) { return this.get( { operation: "retrieve", id: personId }); }; // Custom function to retrieve some people by IDs resource.retrievePeople = function (peopleIdsArray) { return this.query( { operation: "retrievearray", "idsArray[]": peopleIdsArray }); }; // Custom function to save a person object resource.storePerson = function (person, picture) { return this.save( { operation: "store", firstName: person.firstName, lastName: person.lastName }, picture ); }; // Custom function to delete a person object by ID resource.erasePerson = function (personId) { return this.delete( { operation: "erase", id: personId }); }; // Custom function to update the picture of a person resource.updatePersonPicture = function (personId, picture) { return this.updatePicture( { operation: "updatepicture", id: personId }, picture ); }; return resource; }) .factory("PersonResource", function ($resource) { // Construct a resource object that can // interact with the RESTful API of the server. var resource = $resource("people/:operation/:id", { id: "@id", firstName: "@firstName", lastName: "@lastName" }, { // A custom method to update the picture of the person updatePicture: { method: "PUT", isArray: false } } ); // Custom function to retrieve a person resource.prototype.retrieve = function () { return this.$get( { operation: "retrieve" }); }; // Custom function to save a person resource.prototype.store = function () { // Store the current resource instance // to use it later in the handler function. var thisInst = this; // Fallback to the constructor-level "save" method // because the instance-level "$store" method // doesn't let me pass the picture as raw POST data // (it always passes the whole Resource instance as // a JSON object). Return the promise object like the // instance-level methods of a resource object do. return resource.save( { operation: "store", firstName: this.firstName, lastName: this.lastName }, this.picture, function (value) { // Copy to the resource instance // all the properties of the object // returned by the server. angular.copy(value, thisInst); } ).$promise; }; // Custom function to delete a person resource.prototype.erase = function () { return this.$delete( { operation: "erase" }); }; // Custom function to update the picture of a person resource.prototype.updatePicture = function () { // Store the current resource instance // to use it later in the handler function. var thisInst = this; // Fallback to the constructor-level "updatePicture" method // because the instance-level "$updatePicture" method // doesn't let me pass the picture as raw PUT data // (it always passes the whole Resource instance as // a JSON object). Return the promise object like the // instance-level methods of a resource object do. return resource.updatePicture( { operation: "updatepicture", id: this.id }, this.picture, function (value) { // Copy to the resource instance // all the properties of the object // returned by the server. angular.copy(value, thisInst); } ).$promise; }; return resource; }) .controller("mainController", function ($scope, $resource, PeopleService, PersonResource, jsonFilter) { $scope.getWithParams = function (withSuccess) { var callURL = (withSuccess ? "server.php" : "invalid-url.php"); var serverResource = $resource(callURL, { param1: "param1 default", param2: "param2 default" }); var getConfig = {}; if ($scope.getParam1 !== undefined && $scope.getParam1 != "") { getConfig.param1 = $scope.getParam1; } if ($scope.getParam2 !== undefined && $scope.getParam2 != "") { getConfig.param2 = $scope.getParam2; } serverResource.get(getConfig, // Success handler function (value, responseHeaders) { $scope.getWithParamsResult = "GET SUCCESS\n\n" + "value: " + jsonFilter(value) + "\n\n" + "responseHeaders: " + jsonFilter(responseHeaders()); }, // Failure handler function (httpResponse) { $scope.getWithParamsResult = "GET ERROR\n\n" + "httpResponse: " + jsonFilter(httpResponse); } ); }; // Person object constructor var Person = function (id, firstName, lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; }; $scope.getCall = function () { $scope.callName = "getCall"; $scope.callResult = PeopleService.retrievePerson(1); }; $scope.queryCall = function () { $scope.callName = "queryCall"; $scope.callResult = PeopleService.retrievePeople([1, 2, 3]); }; $scope.saveCall = function () { $scope.callName = "saveCall"; var person = new Person(0, "John", "Doe"); $scope.callResult = PeopleService.storePerson(person, "PICTURE_DATA"); }; $scope.deleteCall = function () { $scope.callName = "deleteCall"; $scope.callResult = PeopleService.erasePerson(2); }; $scope.putCall = function () { $scope.callName = "putCall"; $scope.callResult = PeopleService.updatePersonPicture(3, "NEW_PICTURE_DATA"); }; $scope.instGetCall = function () { $scope.instCallName = "instGetCall"; var person = new PersonResource(); person.id = 1; person.retrieve(); $scope.instCallResult = person; }; $scope.instSaveCall = function () { $scope.instCallName = "instSaveCall"; var person = new PersonResource(); person.firstName = "John"; person.lastName = "Doe"; person.picture = "PICTURE_DATA"; person.store(); $scope.instCallResult = person; }; $scope.instDeleteCall = function () { $scope.instCallName = "instDeleteCall"; var person = new PersonResource(); person.id = 2; person.erase(); $scope.instCallResult = person; }; $scope.instPutCall = function () { $scope.instCallName = "instPutCall"; var person = new PersonResource(); person.id = 3; person.picture = "NEW_PICTURE_DATA"; person.updatePicture(); $scope.instCallResult = person; }; });
<?php if ($_SERVER["REQUEST_METHOD"] === "GET") { if (isset($_GET["param1"])) { $param1 = $_GET["param1"]; } else { $param1 = ""; } if (isset($_GET["param2"])) { $param2 = $_GET["param2"]; } else { $param2 = ""; } $result = json_encode(array( "receivedParam1" => $param1, "receivedParam2" => $param2)); echo $result; } ?>
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule people/(.*)$ /code/examples/servercalls/03_ResourceService/people/index.php [NC,L] </IfModule>
<?php $requestPart = explode("people/", $_SERVER['REQUEST_URI']); if (count($requestPart) === 2) { // Remove the URL parameters if any // (from "?" to the end of the string). $qMarkPos = strpos($requestPart[1], "?"); if ($qMarkPos !== false) { $requestPart[1] = substr_replace($requestPart[1], "", $qMarkPos); } $requestPart = explode("/", $requestPart[1]); if (count($requestPart) > 0) { // Operation if (isset($requestPart[0])) { $operation = $requestPart[0]; // Id if (isset($requestPart[1])) { $id = $requestPart[1]; } } } } if ($_SERVER["REQUEST_METHOD"] === "GET") { if ($operation === "retrieve" && isset($id)) { // ...retrieve the person with the requested ID... echo json_encode(array( "id" => $id, "firstName" => "John", "lastName" => "Doe")); } else if ($operation === "retrievearray" && isset($_GET["idsArray"])) { $idsArray = $_GET["idsArray"]; // ...retrieve the people with the requested IDs in $idsArray... $result = array( array( "id" => $idsArray[0], "firstName" => "John", "lastName" => "Doe"), array( "id" => $idsArray[1], "firstName" => "Alice", "lastName" => "White"), array( "id" => $idsArray[2], "firstName" => "James", "lastName" => "Green") ); echo json_encode(array_values($result)); } } else if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($operation === "store") { if (isset($_GET["firstName"])) { $firstName = $_GET["firstName"]; } else { $firstName = ""; } if (isset($_GET["lastName"])) { $lastName = $_GET["lastName"]; } else { $lastName = ""; } if (isset($HTTP_RAW_POST_DATA)) { $picture = $HTTP_RAW_POST_DATA; } else { $picture = ""; } // ...store the person and its picture in the DB assigning it a new ID... $id = 123; echo json_encode(array( "id" => $id, "firstName" => $firstName, "lastName" => $lastName)); } } else if ($_SERVER["REQUEST_METHOD"] === "DELETE") { if ($operation === "erase" && isset($id)) { // ...delete the person with the requested ID from the DB... // Return an object that just confirms the success of the operation echo json_encode(array( "result" => "OK", "message" => "Person " . $id . " deleted")); } } else if ($_SERVER["REQUEST_METHOD"] === "PUT") { if ($operation === "updatepicture" && isset($id)) { $pictureData = file_get_contents('php://input'); if (isset($pictureData)) { // ...update the picture of the person with the requested ID... // Return an object that just confirms the success of the operation echo json_encode(array( "result" => "OK", "message" => "Picture of person " . $id . " updated")); } else { // Return an object that informs of the failure of the operation echo json_encode(array( "result" => "ERROR")); } } } ?>

Example


Description


If you need to deal with a RESTful service exposed by a server, you could use the $http service and create all the server calls on your own, or you could use the $resource service that provides a higher level abstraction built on top of $http.

First of all, to use the $resource service you need to include the angular-resource.js script that is available in the AngularJS files package (as specified in our index.html file). You also have to remember that you must specify a dependency on the ngResource module in your own application modules (as you can see in our script.js file when we declare mainModule and its dependencies).

To be honest, I find the $resource service a little bit messy, but this doesn't mean that it's not useful or usable. You just have to find out if its structure is good for your needs or if it's too limited in some way. Let's take a look at the example to find out.

In point 1 we make a basic GET call to the server to understand the basic structure of the $resource service. Here we interact with server.php on the server side. We start by defining the $resource object passing two parameters to the constructor, the URL and an object with the default values of the parameters param1 and param2. If we want to simulate an error in our example, we pass an invalid URL (invalid-url.php doesn't exist on the server) to the constructor. We also create the configuration object getConfig that is useful to define the actual values of the parameters in case the user specified them. If the parameters are specified then the default values will be overridden by the user-defined values. At this point, we just make a GET call by calling the get method on the Resource object serverResource. This method allows us to specify the configuration object getConfig with the actual values of the parameters, the success handler and the fault handler. In the success handler we can get the JSON object returned by the server through the value argument. We print with the jsonFilter all the objects in the handler's arguments for debugging purposes just to let you see what kind of information you can get from the server call.

Now it's time to move one step forward. We said at the beginning that the $resource service is useful with RESTful endpoints, but we didn't see how yet. Now we're going to see this by illustrating the basic methods it provides. We can divide the methods in two separate categories: constructor-level methods and instance-level methods. All of them send and receive JSON objects, but the main distinction is that the constructor-level ones are available in the object returned by the $resource constructor while the instance-level methods are available in any instance of resource, that is an instance returned by the server or an instance created on the client with the new keyword on the object returned by the $resource constructor.

Point 2 of the example deals with the constructor-level methods. We start by defining the PeopleService factory. This basically represents a server resource where the URL has the people/:operation/:id structure. Here people is the relative URL where our controller server file is (the index.php inside the people directory) and :operation and :id are two parameters that change depending on the server call we want to make. The values of the parameters are taken from the second argument of the $resource constructor that specifies the default values or from the first argument of the constructor-level methods that we'll see soon. In the third argument of the $resource constructor here we also specify a custom method. We want the constructor to make available to us a method called updatePicture that makes a PUT request to the server and returns a single JSON object (isArray is false). In the third argument of the $resource constructor you can specify all your custom methods. Next, we define some functions directly on the resource variable returned by $resource just for our convenience to encapsulate the calls to the constructor-level methods provided by $resource. Let's analyze the first one, retrievePerson. This function accepts a personId as input and sets it as the value of the id parameter together with the operation parameter in the configuration object of the get call. The basic constructor-level methods provided by $resource are get, query, save and delete. Here get issues a GET call to the server with the URL people/retrieve/{personId}, so if the value of personId is 1, the URL for the server call will be people/retrieve/1. Now let's take a quick look at the server side in the people/index.php controller file. The script decodes the operation and id parts of the request URL, for example $operation = "retrieve" and $id = 1, and then returns the appropriate JSON object depending on the received request. This script just returns example objects of course because we're using it simply to demonstrate the interaction with the client side. All the server calls are routed to the people/index.php file thanks to .htaccess that basically says that every request with an URL that starts with people/ must be redirected to the people/index.php file. Now we're ready to summarize what every function added to our resource object does:

  • retrievePerson: calls the get method of $resource that issues a GET server request with the URL format people/retrieve/{personId}, then the server returns a single JSON object representing the requested person
  • retrievePeople: calls the query method of $resource that issues a GET server request with the URL format people/retrievearray/0?idsArray[]={val1}&idsArray[]={val2}&idsArray[]={val3}, then the server returns an array of JSON objects representing the requested people (note that here the ID is 0 because it's the default value of the parameter specified in the $resource constructor and we didn't specify a different one in the configuration object of the query method; the idsArray[] parameter is added to the query string of the URL because it was not specified in the $resource constructor URL with the :param notation, so in general every parameter passed to the configuration object and not specified in the $resource constructor URL is added to the URL query string)
  • storePerson: calls the save method of $resource that issues a POST server request with the URL format people/store/0?firstName={firstName}&lastName={lastName}, then the server returns a single JSON object representing the stored person with the assigned ID (here the storePerson function accepts a person object that simply contains the first and last name of the person and a picture object that represents the profile picture of the person and is passed as the HTTP POST body because it is specified as the second parameter of the save method)
  • erasePerson: calls the delete method of $resource that issues a DELETE server request with the URL format people/erase/{personId}, then the server returns a single JSON object representing the result of the operation
  • updatePersonPicture: calls the updatePicture custom method of $resource that issues a PUT server request with the URL format people/updatepicture/{personId}, then the server returns a single JSON object representing the result of the operation (here the updatePersonPicture function accepts a personId value and a picture object that represents the profile picture of the person and is passed as the HTTP PUT body because it is specified as the second parameter of the updatePicture method)

You can play a little bit with the live example to see each $resource function in action. If you take a look at our index.html file, you can see that we use also the $resolved variable and here is why. We want to hide the result while the response has not been received by the server yet and to do this we need to know when the result object is ready to be displayed. Every call to a $resource function returns a Resource instance object. This object contains a variable named $resolved that when true specifies that the result is ready to be used. The Resource object will have the same properties of the returned JSON object with all the correct values once the response has been received by the server because the $resource service does this automatically, that's why all we need to do is display the object formatted by a json filter to see what we got from the server. In case we want to have a handler to know when the result from the server is ready, we can just call the $resource methods with additional parameters:

  • get, query and delete can be called with the form (config, successHandler, failureHandler)
  • save and updatePicture can be called with the form (config, data, successHandler, failureHandler)

In general, the methods that issue HTTP GET or DELETE requests don't accept a data parameter, while those that issue HTTP POST or PUT requests accept it.

We're now ready to take a look at point 3 of the example that illustrates the use of the instance-level methods provided by the $resource service. Here we want to see ho we can reproduce the functionality of PeopleService, but acting directly on a Resource instance instead of it's constructor object. Every Resource instance has the basic $get, $query, $save and $delete methods. They're equivalent to their constructor-level counterparts in their meaning, but there are some differences. To understand which, let's take a look at our PersonResource factory. Like PeopleService, this factory is generated by the $resource service constructor, but this time you can notice that we use the @ symbol in the configuration object passed to $resource, so when we write @id, @firstName and @lastName we want to tell the configuration object to take the default values of the specified parameters respectively from the id, firstName and lastName properties of the Resource instance (the @ symbol says that the value has to be read from the instance property with the name that follows the symbol). In the $resource constructor we also define a custom updatePicture method like we did in PeopleService and this method will be available in every Resource instance with the $updatePicture name (a $ symbol is automatically added in front of the original name like every other instance-level method). Then we start with the declaration of our utility functions. Notice that we declare them on the prototype of the resource variable because we want to make them available in every Resource instance we'll create and not only on the variable generated by the $resource constructor. When we declare the retrieve function we simply use the $get function of the Resource instance passing it the retrieve value for the operation parameter while the id parameter value is automatically read from the corresponding property in the instance as we said with the configuration object passed to the $resource constructor. The value returned by $get is a promise and can be used to register handlers or for chaining exactly like we can do through the AngularJS $q service. Our store function looks much different from the previous one. Why? The reason is simple. AngularJS provides a $save method in every Resource instance, but it's different from it's constructor-level counterpart because it doesn't let us pass the HTTP POST data. This is an important limit so here I decided to fallback to the constructor-level save method to provide the same functionality in our instance-level function and I can do it just by manually passing the required instance parameters in the configuration object, by passing the picture as HTTP POST data and by registering a success handler function that copies to the Resource instance all the properties of the JSON object returned by the server through the angular.copy utility function (this function removes all the properties in the destination object and copies those of the source object). To finish, I return the $promise object like any other instance-level method of a Resource does. Our erase and updatePicture instance-level functions are similar to what we've just seen for the other two instance-level functions. Now we can write a little summary about our instance-level functions:

  • retrieve: equivalent to the retrievePerson constructor-level function, but calls the $get instance-level method instead
  • store: equivalent to the storePerson constructor-level function and calls the save constructor-level method to pass the HTTP POST data because it cannot be passed with the instance-level $save method
  • erase: equivalent to the erasePerson constructor-level function, but calls the $delete instance-level method instead
  • updatePicture: equivalent to the updatePersonPicture constructor-level function and calls the updatePicture constructor-level method to pass the HTTP PUT data because it cannot be passed with the instance-level $updatePicture method

We didn't use the $query instance-level method because it operates on collections and it wouldn't make much sense to use such a function on a single instance, but we could use it if we want or because it makes sense in our particular use-case. We didn't see yet how we can invoke the instance-level functions we've defined in PersonResource. All we have to do is create an instance of PersonResource by using the new keyword, set the values of the properties we need to define and then call the instance-level function. That's it. If we want, we can register a success and a failure callback handlers on the promise returned by the function to be informed about the response received by the server and know that the returned object is ready to be used.

Wasn't it easy to deal with the $resource service? Maybe yes or maybe not, but it wasn't impossible for sure. It's use depends all on your specific needs.