跳至主要内容

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.

评论

此博客中的热门博文

Build a Reactive application with Angular 5 and Spring Boot 2.0

I have created a post to describe Reactive programming supports in Spring 5 and its subprojects, all codes of this article are updated the latest Spring 5 RELEASE, check spring-reactive-sample under my Github account.
In this post, I will create a simple blog system, including:
A user can sign in and sign out.An authenticated user can create a post.An authenticated user can update a post.Only the user who has ADMIN role can delete a post.All users(including anonymous users) can view post list and post details.An authenticated user can add his comments to a certain post. The backend will be built with the latest Spring 5 reactive stack, including:
Spring Boot 2.0, at the moment the latest version is 2.0.0.M7Spring Data MongoDB supports reactive operations for MongoDBSpring Session adds reactive support for WebSessionSpring Security 5 aligns with Spring 5 reactive stack The frontend is an Angular based SPA and it will be generated by Angular CLI.
The source code is hosted on Github, …

Activating CDI in JSF 2.3

Activating CDI in JSF 2.3 When I upgraed my Java EE 7 sample to the newest Java EE 8, the first thing confused me is the CDI beans are not recoganized in Facelects template in a JSF 2.3 based web applicaiton, which is working in the development version, but in the final release version, they are always resolved as null. I filed an issue on Mojarra and discussed it with the developers from communities and the JSF experts.
According to the content of README, In a JSF 2.3 application, to activate CDI support, declaring a 2.3 versioned faces-config.xml and adding javax.faces.ENABLE_CDI_RESOLVER_CHAIN in web.xml is not enough, you have to declare @FacesConfig annotated class to enable CDI.
Here is the steps I created a workable JSF 2.3 applicatoin in Java EE 8.
Create a Java web application, this can be done easily by NetBeans IDE, or generated by Maven archetype, for exmaple.
$ mvn archetype:generate -DgroupId=com.example -DartifactId=demo -DarchetypeArtifactId=maven-archetype-w…

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…