跳至主要内容

Create a restful application with AngularJS and Grails


Create a restful application with AngularJS and Grails

In this example application, I will create a simple application which basic CRUD operations, and I reused some AngularJS codes of theAngularJS CakePHP Sample.
I assume you have installed the latest JDK 7 and groovy SDK 2.1.9. And also installed Spring Groovy/Grails Toolsuite or an Eclipse based IDE plus Groovy/Grails plugin.

Create a Grails project

Download a copy of Grails distribution from Grails.org. And extract the files into your local disc.
Add <grails>/bin to the system PATH enviroment variable.
Create a project using the following command.
grails create-app <app name>
Enter the app folder root, try to run the project in the embeded tomcat server.
cd <app>
grails run-app
After it is deployed and running, you should see the following like info.
|Running Grails application
|Server running. Browse to http://localhost:8080/angularjs-grails-sample
If you are using Grails 2.3.2, you maybe encounter a critical bug which will cause the above command failed under Windows system.
java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at ../../../src/share/instrument/JPLISAgent.c line: 844
This bug had been marked as Blocker priority, and should be fixed in the upcoming 2.3.3 soon.
More info please refer to GRAILS-10756.
In the comments of this issue, there is a solution provided to overcome this barrier temporarily.
Add the following dependency in the BuildConfig.groovy.
dependencies {
// specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes e.g.
// runtime 'mysql:mysql-connector-java:5.1.24'

 
 build "org.fusesource.jansi:jansi:1.11"
}
Run grails run-app again, you should see the successful info in browser.
grails run-app

Explore the generated codes

By default, the generated project use H2 as database, and Hibernate 3 for the persistence layer. The Hibernate 4 support is included Grails 2.3 and also is ready in the generated codes.
In the BuildConfig.groovy file.
runtime ":hibernate:3.6.10.3" // or ":hibernate4:4.1.11.2"
And in the DataSource.groovy file.
cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory' // Hibernate 3
//    cache.region.factory_class = 'org.hibernate.cache.ehcache.EhCacheRegionFactory' // Hibernate 4
As you see, if you want to switch to Hibernate 4, what you need to do is only comment the Hibernate 3 facilities, and enable the Hibernate 4 alternatives.
Import the generated project as a Grails project into your IDE for the next steps.

Code the backend REST API

Create a entity Book.
grails create-domain-class Book
You can also create it from Eclipse Wizard step by step.
This command will generate Book.groovy and BookSpec.groovy test at the same time.
@Resource(uri='/books')
class Book {

 String title
 String author
 Double price

 static constraints = {
  title blank:false 
  author blank:false   
 }
}
@Resource is a Grails specific annotation. An entity annotated with@Resource will be exposed as REST resource, the basic CRUD operations are ready for use.
URLHTTP MethodDescription
/books.jsonGETGet the list of Books
/books.jsonPOSTCreate a new Book
/books/:id.jsonGETGet the details of a Book
/books/:id.jsonPUTUpdate a Book by id
/books/:id.jsonDELETEDelete a Book by id
Open the BootStrap.groovy file, add some initial data.
def init = { servletContext ->
 new Book(title:"Java Persistence with Hibernate", author:"Gavin King", price:99.00).save()
 new Book(title:"Spring Live", author:"Matt Raible", price:29.00).save() 
}
Run the application, open your browser, navigate tohttp://localhost:8080/angularjs-grails-sample/books.json.
books-json
Grails 2.3 also includes Controller based REST resource producing and other advanced REST features, which are explained in the official document.

Build the frontend AngularJS pages

I reuse the same file structure and some codes from the AngularJS CakePHP Sample directly.

Book List

The template code.
<div ng-controller="BookListCtrl">
    <h1 class="page-header">Book List</h1>
    <table class="table">
        <thead>
            <tr>
                <th width="25px">ID</th>
                <th>TITLE</th>
                <th>PRICE</th>
                <th>AUTHOR</th>
                <th width="50px"></th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="e in books">
                <td>{{e.id}}</td>
                <td>{{e.title}}</td>
                <td>{{e.price|currency}}</td>
                <td>{{e.author}}</td>
                <!-- ng-show="user.id &&user.id==e.user_id" -->
                <td><a ng-click="editBook($index)" ><i
                            class="icon-pencil"></i>
                    </a>
                     
                    <a ng-click="delBook($index)" ><i
                            class="icon-remove-circle"></i></a></td>
            </tr>
        </tbody>
    </table>
<!-- ng-show="user.username" -->
    <p>
        <button type="button" class="btn btn-success" 
                ng-click="addBook()">
            <b class="icon-plus-sign"></b>Add Book
        </button>
    </p>
</div>
The BookListCtrl code.
as.controller('BookListCtrl', function($scope, $rootScope, $http, $location) {
var load = function() {
    console.log('call load()...');
    $http.get($rootScope.appUrl + '/books.json')
     .success(function(data, status, headers, config) {
  $scope.books = data;
  angular.copy($scope.books, $scope.copy);
     });
}

load();

$scope.addBook = function() {
    console.log('call addBook');
    $location.path("/new");
}

$scope.editBook = function(index) {
    console.log('call editBook');
    $location.path('/edit/' + $scope.books[index].id);
}

$scope.delBook = function(index) {
    console.log('call delBook');
    var todel = $scope.books[index];
    $http
     .delete($rootScope.appUrl + '/books/' + todel.id + '.json')
     .success(function(data, status, headers, config) {
  load();
     }).error(function(data, status, headers, config) {
    });
}

});

Add a new Book

The template code.
<div ng-controller="NewBookCtrl">
 <form class="form-horizontal">
  <h1 class="page-header">New Book</h1>
  <fieldset>
   <div class="control-group">
    <label class="control-label" for="title">Title</label>
    <div class="controls">
     <input id="title" type="text" class="input-block-level"
      required="true" ng-model="book.title"></input>
    </div>
   </div>
   <div class="control-group">
    <label class="control-label" for="title">Author</label>
    <div class="controls">
     <input id="title" type="text" class="input-block-level"
      required="true" ng-model="book.author"></input>
    </div>
   </div>
   <div class="control-group">
    <label class="control-label" for="title">Price</label>
    <div class="controls">
     <input id="title" type="number" class="input-block-level"
      required="true" ng-model="book.price"></input>
    </div>
   </div>
  </fieldset>
  <div class="form-actions">
   <button class="btn btn-primary" ng-click="saveBook()">Add
    Book</button>
  </div>
 </form>
</div>
The NewBookCtrl code.
as.controller('NewBookCtrl', function($scope, $rootScope, $http, $location) {

$scope.book = {};

$scope.saveBook = function() {
    console.log('call saveBook');
    $http
     .post($rootScope.appUrl + '/books.json', $scope.book)
     .success(function(data, status, headers, config) {
  $location.path('/books');
     }).error(function(data, status, headers, config) {
    });
}
});

Edit a Book

The template code.
<div ng-controller="EditBookCtrl">
 <form class="form-horizontal">
  <h1 class="page-header">Edit Book({{book.id}})</h1>
  <fieldset>
   <div class="control-group">
    <label class="control-label" for="title">Title</label>
    <div class="controls">
     <input id="title" type="text" class="input-block-level"
      required="true" ng-model="book.title"></input>
    </div>
   </div>
   <div class="control-group">
    <label class="control-label" for="title">Author</label>
    <div class="controls">
     <input id="title" type="text" class="input-block-level"
      required="true" ng-model="book.author"></input>
    </div>
   </div>
   <div class="control-group">
    <label class="control-label" for="title">Price</label>
    <div class="controls">
     <input id="title" type="number" class="input-block-level"
      required="true" ng-model="book.price"></input>
    </div>
   </div>
  </fieldset>
  <div class="form-actions">
   <button ng-click="updateBook()" class="btn btn-primary">Update
    Book</button>
  </div>
 </form>
</div>
The EditBookCtrl code.
as.controller('EditBookCtrl', function($scope, $rootScope, $http, $routeParams, $location) {

var load = function() {
    console.log('call load()...');
    $http.get($rootScope.appUrl + '/books/' + $routeParams['id'] + '.json')
     .success(function(data, status, headers, config) {
  $scope.book = data;
  angular.copy($scope.book, $scope.copy);
     });
}

load();

$scope.book = {};

$scope.updateBook = function() {
    console.log('call updateBook');
    $http
     .put($rootScope.appUrl + '/books/' + $scope.book.id + '.json',  $scope.book)
     .success(function(data, status, headers, config) {
  $location.path('/books');
     }).error(function(data, status, headers, config) {
    });
}
});

Browse the frontend UI

frontend-ui
Try add a new Book and edit and delete it.

Summary

In this example application, Grails was used to produce the backend REST API, it is also simple and stupid work. Currently, I have not added security feature, maybe add it in future.
发表评论

此博客中的热门博文

JPA 2.1: Attribute Converter

JPA 2.1: Attribute Converter If you are using Hibernate, and want a customized type is supported in your Entity class, you could have to write a custom Hibernate Type. JPA 2.1 brings a new feature named attribute converter, which can help you convert your custom class type to JPA supported type. Create an Entity Reuse thePostentity class as example. @Entity @Table(name="POSTS") public class Post implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="ID") private Long id; @Column(name="TITLE") private String title; @Column(name="BODY") private String body; @Temporal(javax.persistence.TemporalType.DATE) @Column(name="CREATED") private Date created; @Column(name="TAGS") private List<String> tags=new ArrayList<>(); } Create an attribute converter In this example…

Spring Data Mongo: bridge MongoDB and Spring

Spring Data Mongo: bridge MongoDB and SpringMongoDBis one of the most popular NoSQL products, Spring Data Mongo(Maven archetype id is spring-data-mongodb) tries to provides a simple approach to access MongoDB. Configuration Add the following code fragments in your Spring configuration. <!-- Mongo config --> <mongo:db-factory id="mongoDbFactory" host="localhost" port="27017" dbname="conference-db" /> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg ref="mongoDbFactory" /> </bean> <mongo:repositories base-package="com.hantsylabs.example.spring.mongo" /> Firstly, declare aDbFactorywhich is responsible for connecting to the MongoDB server. Then, register theMongoTemplateobject which envelop the data operations on MongoDB. Lastly addmongo:repositorieswith an essentialbase-packageattribute, Spring will discovery your d…

Auditing with Hibernate Envers

Auditing with Hibernate Envers The approaches provided in JPA lifecyle hook and Spring Data auditing only track the creation and last modification info of an Entity, but all the modification history are not tracked. Hibernate Envers fills the blank table. Since Hibernate 3.5, Envers is part of Hibernate core project. Configuration Configure Hibernate Envers in your project is very simple, just need to addhibernate-enversas project dependency. <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency> Done. No need extra Event listeners configuration as the early version. Basic Usage Hibernate Envers provides a simple@Auditedannotation, you can place it on an Entity class or property of an Entity. @Audited private String description; If@Auditedannotation is placed on a property, this property can be tracked.