使用 Grails 和 AngularJS 1.x 构建 REST API
使用 Grails rest api 配置文件为既有的 AngularJS 应用程序(Angular 1)构建后端
作者:Sergio del Amo
Grails 版本 3.3.0
1 Grails 培训
Grails 培训 - 由创建并积极维护 Grails 框架的人员开发和提供!
2 开始
在本指南中,你将连接一个现有的 AngularJS 待办事项应用至 Grails 后端。
2.1 你需要哪些
若要完成此指南,你需要具备以下内容
-
一些时间
-
像样的文本编辑器或 IDE
-
JDK 1.8 或更高版本已安装且
JAVA_HOME
已妥善配置
2.2 如何完成该指南
若要开始,请执行以下操作
-
下载并解压缩源代码
或
-
克隆 Git 存储库
git clone https://github.com/grails-guides/grails-restapi-angularjs.git
Grails 指南存储库包含两个文件夹
-
initial
初始项目。通常是一个简单的 Grails 应用,并包含一些其他代码,让你能快速上手。 -
complete
已完成的示例。这是对指南所提供步骤进行操作以及对initial
文件夹应用这些更改的结果。
若要完成该指南,请转至 initial
文件夹
-
进入
grails-guides/grails-restapi-angularjs/initial
然后按照下一部分中的说明操作。
如果你进入 grails-guides/grails-restapi-angularjs/complete ,则可以直接转到完成的示例 |
3 初始 Angular 应用程序
本部分展示在 Treehouse AngularJS 教程 期间开发的代码。在该教程中,会开发一个 AngularJS 应用程序。该应用程序是一个待办事项列表管理器。
初始化的应用程序未连接到任何后端。如果您刷新浏览器,待办事项更改将丢失。 |
index.html
是我们 AngularJS 应用程序的入口点。
<!doctype html>
<html lang="en">
<head>
<title></title>
<link href='https://fonts.googleapis.com/css?family=Varela+Round' rel='stylesheet' type='text/css'>
<link href='styles/main.css' rel='stylesheet' type="text/css">
</head>
<body ng-app="todoListApp">
<h1>My TODOs</h1>
<todos></todos>
<script src="vendor/angular.js" type="text/javascript"></script>
<script src="scripts/app.js" type="text/javascript"></script>
<script src="scripts/controllers/main.js" type="text/javascript"></script>
<script src="scripts/services/data.js" type="text/javascript"></script>
<script src="scripts/directives/todos.js" type="text/javascript"></script>
</body>
</html>
我们在 app.js
中创建 AngularJS 应用程序。
angular.module("todoListApp", []);
一个 AngularJS 控制器负责管理应用程序流。
'use strict';
angular.module('todoListApp')
.controller('mainCtrl', function($scope, dataService) {
$scope.addTodo = function() {
var todo = {name: "This is a new todo."};
$scope.todos.unshift(todo);
};
dataService.getTodos(function(response) {
$scope.todos = response.data;
});
$scope.deleteTodo = function(todo, $index) {
dataService.deleteTodo(todo);
$scope.todos.splice($index, 1);
};
$scope.saveTodo = function(todo) {
dataService.saveTodo(todo);
};
})
我们有一个 todos
指令。此指令使用前一个控制器和下一个模板。
angular.module('todoListApp')
.directive('todos', function() {
return {
templateUrl: 'templates/todos.html',
controller: 'mainCtrl',
replace: true
}
})
<div class="list">
<div class="add">
<a href="" ng-click="addTodo()">+ Add a New Task</a>
</div>
<div class="item" ng-class="{'editing-item': editing, 'edited': todo.edited, 'completed': todo.completed}"
ng-repeat="todo in todos | orderBy: 'completed'" ng-init="todo.completed = false">
<input ng-model="todo.completed" type="checkbox"/>
<span ng-click="todo.completed = !todo.completed; todo.edited = true"></span>
<label ng-click="editing = true" ng-hide="editing">
{{todo.name}}</label>
<input ng-change="todo.edited = true" ng-blur="editing = false;" ng-show="editing" ng-model="todo.name" class="editing-label" type="text"/>
<div class="actions">
<a href="" ng-click="saveTodo(todo)">Save</a>
<a href="" ng-click="deleteTodo(todo, $index)" class="Delete">delete</a>
</div>
</div>
</div>
我们的数据服务当前正在从一个模拟的 json 文件中加载待办事项。
'use strict';
angular.module('todoListApp')
.service('dataService', function($http) {
this.getTodos = function(callback){
$http.get('mock/todos.json')
.then(callback)
};
this.deleteTodo = function(todo) {
console.log("The " + todo.name + " todo has been deleted!")
};
this.saveTodo = function(todo) {
console.log("The " + todo.name + " todo has been saved!");
};
});
[
{"name": "clean the house"},
{"name": "water the dog"},
{"name": "feed the lawn"},
{"name": "pay dem bills"},
{"name": "run"},
{"name": "swim"}
]
4 编写 Grails 应用程序
我们即将处理的初始项目是使用 Grails REST 配置 生成的。
grails create-app myapp --profile=rest-api
4.1 域类
创建持久实体来存储待办事项。在 Grails 中处理持久性的最常用方式是使用 Grails 域类
一个域类符合 Model View Controller (MVC) 模式中的 M,并代表映射到一个底层数据库表的持久实体。在 Grails 中,一个域存在于 grails-app/domain 目录中的一个类。
complete$ ./grailsw create-domain-class Todo
Todo
域类是我们的数据模型。我们定义不同的属性来存储 Todo
特征。
package demo
import grails.rest.Resource
@Resource(uri='/todos')
class Todo {
String name
boolean completed
static constraints = {
}
}
通过一个 单元测试,我们测试 name
是否是一个必需属性。
package demo
import spock.lang.Specification
import grails.testing.gorm.DomainUnitTest
class TodoSpec extends Specification implements DomainUnitTest<Todo> {
void "test name is required"() {
when:
def todo = new Todo(name: name)
then:
!todo.validate()
todo.errors['name'].code == errorCode
where:
name | errorCode
null | 'nullable'
'' | 'nullable'
}
}
然后我们在 BootStrap.groovy
中加载一些测试数据。
package demo
class BootStrap {
def init = { servletContext ->
def todos = [
[name: 'clean the house'],
[name: 'water the dog'],
[name: 'feed the lawn'],
[name: 'pay dem bills'],
[name: 'run'],
[name: 'swim']
].each { new Todo(name: it.name).save() }
}
def destroy = {
}
}
我们用 @Resource
注解该域类。
import grails.rest.Resource
@Resource(uri='/todos')
class Todo {
这个注解将 Todo
域类用作 REST 资源
在 Grails 中创建 RESTful API 最简单的方法是将一个域类用作 REST 资源。只需添加 Resource 转换并指定一个 URI,即可自动将您的域类以 XML 或 JSON 格式作为 REST 资源使用。该转换将自动创建一个名为 TodoController 的控制器并注册必要的 RESTful URL 映射
这是我们域类的注册 URL 映射。
HTTP 方法 URI Grails 操作 |
URI |
Grails 操作 |
GET |
/todos |
index |
GET |
/todos/create |
create |
POST |
/todos |
save |
GET |
/todos/${id} |
show |
GET |
/todos/${id}/edit |
edit |
PUT |
/todos/${id} |
update |
DELETE |
/todos/${id} |
delete |
4.2 启用 CORS
由于客户端(AngularJS)和服务端(Grails)将分别在不同的端口上运行,CORS 配置是必需的。
修改您的 application.yml
以启用 CORS
grails:
cors:
enabled: true
5 将 Angular 应用程序连接到 Grails
我们添加一个指令,用于检测用户在编辑 Todo 名称时按下 ENTER 时
<script src="scripts/directives/ngEnter.js" type="text/javascript"></script>
angular.module('todoListApp')
.directive('ngEnter', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.ngEnter, {'event': event});
});
event.preventDefault();
}
});
};
});
我们将稍稍修改一下 UI。例如,我们将移除保存按钮,如果用户修改了 todo(更改名称或完成),我们将在服务端保存更改。
<div class="list">
<div class="add">
<a href="" ng-click="addTodo()">+ Add a New Task</a>
</div>
<div class="item" ng-class="{'editing-item': editing, 'edited': todo.edited, 'completed': todo.completed}"
ng-repeat="todo in todos | orderBy: 'completed'">
<input ng-model="todo.completed" type="checkbox"/>
<span ng-click="todo.completed = !todo.completed; todo.edited = true;saveTodo(todo);"></span>
<label ng-click="editing = true" ng-hide="editing">
{{todo.name}}</label>
<input ng-enter="editing=false;saveTodo(todo)" ng-change="todo.edited = true" ng-blur="editing = false;" ng-show="editing" ng-model="todo.name" class="editing-label" type="text"/>
<div class="actions">
<a href="" ng-click="deleteTodo(todo, $index)" class="Delete">delete</a>
</div>
</div>
</div>
'use strict';
angular.module('todoListApp')
.controller('mainCtrl', function($scope, dataService) {
$scope.addTodo = function() {
dataService.addTodo(function(response) {
$scope.todos.unshift(response.data);
});
};
dataService.getTodos(function(response) {
$scope.todos = response.data;
});
$scope.deleteTodo = function(todo, $index) {
dataService.deleteTodo(todo, function(response) {
$scope.todos.splice($index, 1);
});
};
$scope.saveTodo = function(todo, $index) {
dataService.saveTodo(todo, function(response) {
console.log(response.data)
});
};
})
数据服务现在连接到 Grails 后端,而不是加载模拟 JSON 文件。
'use strict';
angular.module('todoListApp')
.service('dataService', function($http) {
var todosGrailsServerUri = 'http://localhost:8080/todos';
this.getTodos = function(callback){
$http.get(todosGrailsServerUri).then(callback)
};
this.addTodo = function(callback) {
var todo = {name: "This is a new todo.", completed: false};
$http.post(todosGrailsServerUri,todo).then(callback);
}
this.deleteTodo = function(todo, callback) {
$http.delete(todosGrailsServerUri + '/' + todo.id).then(callback);
};
this.saveTodo = function(todo, callback) {
$http.put(todosGrailsServerUri + '/' + todo.id,todo).then(callback);
};
});
6 运行应用程序
要运行应用程序,请使用 ./gradlew bootRun
命令,该命令将在 8080 端口上启动应用程序。
打开您的 angular 应用程序,您将享受由 Grails 后端提供支持的 todo 管理器。您可以在不丢失更改的情况下刷新浏览器。