Olá Pessoal,
No post de hoje vamos ver como persistir os dados que estão no nosso front-end com AngularJS em uma base de dados, mas o nosso back-end é Java usando Spring, Hibernate e Jersey.
Lets go…
Starting…
Vou considerar que você já conhece Spring e Hibernate, portanto o post irá direto ao assunto. Veremos apenas como fazer com que os dados do front-end chegue ao back-end. É necessário que você tenha o Jersey no Controller do lado do Server-side. Fiz um post sobre o AngularJS e Jersey.
Antes de começar
- Crie um projeto webapp, de preferência com o maven;
- Adicione as dependências do Spring, hibernate, banco de dados, Jersey;
- Tenha a lib do angular no projeto ou use a versão online.
O nosso projeto
É muito simples, apenas um formulário que cadastra um customer. Veja o projeto:
Vou considerar que você já tem a camada de serviço DAO e application-context do Spring devidamente criados e funcionando.
Adicionando Dependência no pom.xm Jersey-Spring
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-spring</artifactId>
<version>1.8</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
Configurando o Jersey-Spring
No arquivo web.xml deixe assim:
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<!– Aqui é o package onde vai ficar o controller –>
<param-value>com.camilolopes.jersey.services</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
Crie a classe CustomerController.java conforme a seguir:
@Controller
@Path(“/service”)
public class CustomerController {
@Autowired
@Qualifier(“customerService”)
private CustomerService customerService; //substituir pela sua classe de Service gerenciada pelo Spring
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Customer> getCustomer() {
List<Customer> list = customerService.getListCustomers();
return list;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public void saveCustomer(Customer customer){
customerService.save(customer);
}
public CustomerService getCustomerService() {
return customerService;
}
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
}
Se você viu o nosso post com Jersey e conhece WebService já sabe o que significa essa classe. Em poucas palavras, é através dessa classe que o front-end e o back-end se comunicam através de um objeto JSON. Então quando o angular precisa enviar algo para o back-end ele vai precisar chamar algum serviço disponível nessa classe, e temos apenas dois: um GET e outro POST.
Criando o app.js
$app = angular.module(‘app’,[‘ngResource’]);
$app.config(function($routeProvider,$httpProvider,$locationProvider){
//routes
$routeProvider.
when(‘/’,{templateUrl:’view/customers.html’,controller:customerController}).
when(‘/create’,{templateUrl:’view/form.html’,controller:customerController}).
when(‘/list’,{templateUrl:’view/customers.html’,controller:customerController}).
otherwise(
{
redirectTo:’/’
});
$httpProvider.responseInterceptors.push(function($q,$rootScope){
return function(promise){
return promise.then(function(response){
return (response);
},function(response){
$data = response.data;
$error = $data.error;
if($error && $error.text){
console.log(“ERROR: ” + $error.text);
}
else{
if(response.status=404)
console.log(“page not found”);
}
return $q.reject(response);
});
};
});
});
Esse código acima apenas cria rotas com base no que for chamado no browser. Por exemplo, se chamarmos /create vai ser carregado a página form.html e o controller customerController que ainda vamos criar.
A outra função é, em caso de erro no response, podermos exibir um conteúdo customizado. A novidade que temos nesse arquivo é:
$app = angular.module(‘app’,[‘ngResource’]);
Lembra que nos posts anteriores passávamos [] para o segundo parâmetro do module? Agora estamos dizendo que vamos usar ng-resource para conectar a um WebService. Adicione ao seu projeto angular-resource.js ou use a versão online:
<script src=“http://code.angularjs.org/1.0.6/angular-resource.min.js”></script>
Criando customerController.js
Vamos agora criar o controller do angular, portanto crie um arquivo JavaScript chamado customerController.js. Não vamos organizar o projeto, pode colocar dentro de webapp mesmo:
function customerController($scope,$resource,$location){
//estamos criando um objeto e linkando com o webservice CustomerController.java
Customer = $resource(“rest/service/”);
//estamos criando uma função que carrega todos os customers.
$scope.loadAll = function(){
Customer.query(
function(data){
$scope.rows = data;
});
};
//quando chamado cria um novo customer e salva.
$scope.newOne = function() {
var c = new Customer();
//estou atribuindo o nome digitado ao atributo do domain Customer.
c.name = $scope.nameCustomer;
c.$save();
};
}
No inicio parece estranho, mas é porque é diferente do que estamos acostumados. Coloquei a explicação em modo de comentário inline para facilitar o entendimento. Não se preocupe se no inicio se sentir desconfortável com a estrutura, também me senti, mas com o tempo fui aprendendo melhor e vendo que faz sentindo a forma que o angular trata o binding.
Criando o arquivo index.html
Vamos carregar as libs, então esse arquivo terá libs e a ng-view:
<html ng-app=“app”>
<head>
<meta charset=“UTF-8”>
<script src=“http://code.angularjs.org/1.0.6/angular.min.js”></script>
<script src=“app.js”></script>
<script src=“customerController.js”></script>
</head>
<body>
<a href=“#/create”>Create</a>
<a href=“#/list”>List</a>
<script src=“http://code.angularjs.org/1.0.6/angular-resource.min.js”></script>
<div ng-view></div>
</body>
</html>
Criando o form.html
<body ng-controller=“customerController”>
<h2>Register Customer</h2>
<form name=“customerform”>
<div>
<label>Name</label>
<input name=“name” ng-model=“nameCustomer” require/> {{nameCustomer}}
</div>
<div>
<label>Phone</label>
<input name=“phone” ng-model=“phoneCustomer” /> {{phoneCustomer}}
</div>
<div>
<label>E-mail</label>
<input name=“email” ng-model=“emailCustomer” type=“email” required /> {{emailCustomer}}
</div>
<div>
<input type=“button” value=“Save” ng-click=“newOne()” ng-disabled=“customerform.$invalid”/>
</div>
</form>
</body>
Criando customers.html
Aqui vamos listar os customers cadastrados
<div ng-init=“loadAll()”>
<table id=“tableData”>
<thead>
<tr>
<th>ID</th>
<td>Nome</td>
</tr>
</thead>
<tbody>
<tr ng-repeat=“row in rows”>
<td>{{row.id}}</td>
<td>{{row.name}}</td>
</tr>
</tbody>
</table>
</div>
Observe que temos ng-init. Essa diretiva invoca o método que carrega todos os customers.
Em seguida temos a diretiva ng-repeat que funciona como um forEach do Java rows; é a variável que criamos no customerController.js
Testando
Assumindo que seu BD está up, suba a aplicação (caso tenha criado um maven Project, apenas digite mvn tomcat:run via linha de comando):
Clique no link Create
Observe a url e o botão save. A url tem alguma relação com as rotas que criamos? E o botão save, por que está desabilitado?
Ele está desabilitado porque fizemos isso no form.html:
<input type=“button” value=“Save” ng-click=“newOne()” ng-disabled=“customerform.$invalid”/>
Olhe para a diretiva ng-disabled. Ali estamos dizendo que se o formulário for invalid desabilite o botão. E o que é um formulário inválido? Nesse caso, se os campos que marcamos como required não estiverem preenchidos, é um form inválido. Observe que quando preencher os campos requeridos, automaticamente o botão fica habilitado:
Clique em save. Se clicar mais de uma vez acontece isso:
Click no link List
Poderiamos redirencionar usando $location.path(“nomeDaRota”);
Apesar de termos vários outros atributos no form, salvamos apenas o name. Mas sinta-se a vontade em praticar e salvar os demais. Conferindo no banco:
Pronto. Salvando dados no BD com AngularJS, Spring, Jersey, Hibernate. Simples, não?
O projeto completo está no meu GitHub no repositório dedicado às minhas brincadeiras com angularJS: https://github.com/camilolopes/workspaceAngularJs
Sempre estarei subindo projetos novos, brincando com o framework… Se quiser acompanhar basta me seguir no github.
Enfim, espero que tenham gostado do post.
Abraços, see ya!!
Gostei do post cara.
Uma coisa para melhorar no blog é a parte onde vai os códigos.
Essa maneira aí fica muito ruim de ler. Perde toda a identação.
Já vi em outros blogs uns editores de código bem maneiros.
Claro que não estou mandando vc alterar. Longe de mim querer passar essa impressão.
Só acho que iria “agregar valor” ao seu blog.
Opa Lazaro,
Obrigado por comentar e mandar feedback. Pois é, eu tb concordo com você. É horrivel a parte do código, estou em busca de algum plugin confiável para adicionar ao wordpress para código, eu usava um. Dai, atualizei o wordpress e parou de funcionar e o plugin morreu, e o que aconteceu com todos os posts que tinha o plugin? quebraram e feio. Por isso que hoje, coloco dessa forma, ate encontrar um plugin bacana. se souber de algum para indicar agradeço. acordei pensando nisso hj. vlw.
Muito bom post. Gostaria de ver uma versão de integração de AngularJS+JSF
olá Guilherme,
Sao tecnologias com foco diferente, tentar fazer essa integração com a versao do JSF hj, não vejo valor nenhum, sem falar no tamanho do esforço que é. Cada uma nasceu em épocas diferentes com propositos de resolução para problemas distintos.
Eu particularmente nao consigo mais usar JSF. E olha que sou fã do framework desde da vers.ao 1.x.
abraço,
Bom dia Camilo! Parabéns pelo belo seu post. Gostaria de saber se você acha viável a integração do spring security com essa arquitetura apresentada no post, ou se você sugere alguma outra ferramenta para fazer o papel do Spring Security no controle de acesso e autenticação de usuário do sistema.
Desde já, muito obrigado!
opa Luciano,
Então usamos a integração com spring security e funciona bem. Tb implementamos com OAuth.
Pode ir na fé que fica bão.
abraço,