vendredi 15 février 2013

Consume a RESTful service with AngularJS


In the beginning of this year, I watched the AngularJS's talk at Devoxx onParleys. After that, I went to ParisJUG to see the technical talk about AngularJS.
And I thought: it seems to be a great JavaScript framework, I must try it!
To try it, I wrote a small application which consume a REST service.
The application is similar to the application I wrote for the post: put it alltogether, and consumes the same REST service.
1 - Call a REST service in AngularJS.
Normally in AngularJS if you want to call a REST service, you have to write something like this:
angular.module('myModule', ['ngResource'], function($provide) {
    $provide.factory('musics', ['$resource', function($resource) {
        return {
            getArtistBeginBy: function(beginByP) {
                return $resource('http://localhost\\:8080/RESTfulServices/rs/ArtisteNameBeginningBy/:beginBy').get(
                {
                    beginBy: beginByP
                } 
                );
            }
        }
    }]);
});

function SearchCtrl($scope, $resource, musics) {
            
    $scope.search = function() {                
        $scope.listMusic = musics.getArtistBeginBy($scope.yourSearch);                  
    }   
}     
But because, my REST service uses   JAXB as JSON serializer, I have an issue when I have "a single element inarray".
My service returns something like this:
{"music":{"albumTitle":"A tout moment","artisteName":"Eiffel","id":"14"}}
Instead of:
{"music":[{"albumTitle":"A tout moment","artisteName":"Eiffel","id":"14"}]}
And ng-repeat of AngularJS don't like that ;)

To work around this issue, I alter the data returned by $resource.
When the data returned by $resource are not empty (not undefined) and are not an array, I'm in case of "a single element in array", then:
  • I create an array
  • I put the data returned by $resource (single element)  in the array
  • and I replace the data returned by $resource by the array 
The code to call the REST service with the workaround:
js/search.js
angular.module('myModule', ['ngResource'], function($provide) {
    $provide.factory('musics', ['$resource', function($resource) {
        return {
            getArtistBeginBy: function(beginByP) {
                return $resource('http://localhost\\:8080/RESTfulServices/rs/ArtisteNameBeginningBy/:beginBy').get(
                {
                    beginBy: beginByP
                } , //params
        
                function (data) {   //success
                        
                    if (! (data['music'] instanceof Array)){
                        if (typeof data['music'] != 'undefined'){ 
                            var myArray = new Array();
                            myArray.push(data['music']);
                            data['music'] =  myArray;           
                        }                                                       
                    }
                },
                function (data) {   //failure
                    //error handling goes here
                    }
                    );
            }
        }
    }]);
});

function SearchCtrl($scope, $resource, musics) {
    
    $scope.search = function() {                
                $scope.listMusic = musics.getArtistBeginBy($scope.yourSearch);                  
    }   
}  
2 - Use the service and bind the result in the html page.
index.html
<!doctype html>
<html ng-app="myModule">    
    <head>
        <link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular-resource.js"></script>
        <script src="./js/search.js"></script>     
    </head>
    <body ng-controller="SearchCtrl">
        <div>
            <br>
            <input type="text" ng-model="yourSearch" class="search-query" placeholder="Enter an artist name here" ng-change="search()">
            <hr>

            <table border="1" class="table table-striped">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Artist Name</th>
                        <th>Album Name</th>
                    </tr>
                </thead>

                <tbody> 
                    <tr ng-repeat="m in listMusic.music">
                        <td>{{m.id}}</td>
                        <td>{{m.artisteName}}</td>
                        <td>{{m.albumTitle}}</td>
                    </tr>
                </tbody>         
            </table>  
        </div>
    </body>
</html>
3 - Add  Cross-origine resource sharing (CORS) to Jersey REST services
And finally, because I want to call my REST service from any domain, I add CORS to Jersey REST Service.
Now, I can have a standalone Angularjs's application (outside the web application).
To do that, I add a filter to the Jersey servlet.

The filter
ResponseCorsFilter.java
package paddy.jersey.cors;
 
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;

public class ResponseCorsFilter implements ContainerResponseFilter {
 
    @Override
    public ContainerResponse filter(ContainerRequest req, ContainerResponse contResp) {
                
        contResp.getHttpHeaders().putSingle("Access-Control-Allow-Origin", "*");
        contResp.getHttpHeaders().putSingle("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
 
        String reqHead = req.getHeaderValue("Access-Control-Request-Headers");
        contResp.getHttpHeaders().putSingle("Access-Control-Allow-Headers", reqHead);
               
        return contResp;
    }
}
Filter declaration
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
        <servlet-name>ServletAdaptor</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
            <param-value>paddy.jersey.cors.ResponseCorsFilter</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>ServletAdaptor</servlet-name>
        <url-pattern>/rs/*</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
</web-app>


AngularJS standalone source code

NetBeans project of the service with CORS

Aucun commentaire: