Com o advento do MVC, muito se ouviu falar nas vantagens que esse novo padrão traria para os projetos e suas respectivas e consequentes produtividade e facilidade. Por muito tempo, entretanto, esse era um conceito aplicado tão-somente ao universo server side, uma vez que o front-end era sempre feito visando muito mais estrutura (HTML) que divisão em camadas, negócio e responsabilidades programáticas. Esse paralelo fez muita gente desacreditar do AngularJS no começo, uma vez que o mesmo vinha exatamente para quebrar esse tabu e injetar mais lógica no cliente, muito mais, evitando que execução desnecessária fosse enviada ao servidor que se encarrega, agora, das funções mais importantes da aplicação, como salvar dados, fazer integrações ou validações mais pesadas que dependam de outros componentes, por exemplo.

Mais que isso, o AngularJS permite estender a HTML convencional e adicionar views dinâmicas de modo a criar um vocabulário próprio. Desenvolvido e mantido pelo Google, o projeto segue à risca o padrão MVC, onde as views são especificadas usando o HTML + AngularJS da própria linguagem de template; os modelos e controladores, por sua vez, são especificados através de objetos e funções do JavaScript. Dentre outras vantagens, ele permite organizar melhor o seu código JavaScript (uma vez que as diretivas permitem abstrair muito do código que faríamos em um projeto normal), criar aplicações responsivas e rápidas, otimizadas para o universo HTTP bem como facilmente integráveis com outros frameworks JavaScript, como o jQuery.

Neste artigo faremos um overview acerca dos seus principais recursos através do desenvolvimento de uma aplicação de cadastro de notícias para um site, desde a configuração do ambiente, a criação do serviço no servidor, até os testes e muito mais.

Atualidades do Angular

Concepção do projeto

O nosso projeto parte do princípio de que o leitor já tem conhecimentos básicos de HTML, CSS e JavaScript, bem como da utilização de ferramentas de edição de código, como Notepad++, Sublime, etc.

Ele basicamente será composto por dois projetos principais: um projeto feito em Java Web, usando a tecnologia Restful da API JAX-RS para fornecer os métodos de comunicação back-end que a nossa aplicação precisará consumir no cliente. Esse tipo de comunicação pode ser feito em qualquer plataforma que o leitor se sentir à vontade, desde que o mesmo tenha conhecimentos sobre Web Services o suficiente para abstrair toda a camada de negócio do sistema; e um segundo projeto que será o principal, no caso o próprio AngularJS.

Para a primeira parte, referente ao back-end, faremos uso das seguintes tecnologias/ferramentas:

  • Java JDK, em sua versão 7 ou superior;
  • Eclipse for Java EE Developers (versão para web), em sua versão Mars ou superior;
  • Servidor Tomcat (da Apache), na sua versão 7 - em zip (já estamos na versão 8.0, mas para os propósitos do artigo a 7 é mais que suficiente);
  • Banco de Dados MySQL e gerenciador gráfico Workbench;
  • Notepad++ para a edição dos arquivos do client side.

Para a concepção do banco de dados o leitor também pode optar por qualquer outro banco de sua preferência, lembrando que não mostraremos aqui detalhes de abstração dessa camada. O mesmo vale para a linguagem de programação e plataforma de comunicação dos serviços, que pode ser facilmente substituída por Node.js, por exemplo.

Na Figura 1 temos a representação do modelo Entidade Relacionamento no MySQL do nosso projeto em questão. Veja que temos apenas duas tabelas para representar todo o salvamento de dados do projeto, a saber:

  • tb_noticia: se encarrega de salvar todos os dados inerentes a uma notícia do portal, com campos como título, descrição, texto completo da notícia (que pode inclusive já ser salvo como texto HTML), a data da sua publicação e um status para verificar se a mesma encontra-se disponível ou bloqueada;
  • tb_imagem: será associada à tabela tb_noticia para salvar todas as imagens que possam existir na mesma, já que é na base de dados que guardaremos esse tipo de conteúdo. Contém campos de título, descrição e do arquivo propriamente dito, além da coluna de chave estrangeira que estabelece o relacionamento de um-para-muitos com a referida tabela.

Lembrando que o leitor precisa ainda exportar esse modelo para a base de dados física propriamente dita. Para isso, poderá fazer de duas formas:

  1. através da opção de menu Database > Forward Engineer, vai clicando em Next até o fim;
  2. através da importação do arquivo de script .sql que se encontra disponível no pacote de fontes deste projeto e execução do mesmo no seu banco.

No exemplo, criamos a base com o esquema de nome “News_devmedia”, mas pode dar o nome que melhor desejar.

Modelo ER da base de dados do projeto de notícias
Figura 1. Modelo ER da base de dados do projeto de notícias.

Mãos à obra

Para início de conversa, precisamos configurar uma estrutura básica que permita ao usuário da aplicação se autenticar e, assim, filtrar quem pode ou não acessar o conteúdo da mesma. Para não aumentar a complexidade da implementação vamos abstrair a parte de acesso a banco e criação de login ou criptografia de dados para essa parte da aplicação. Em vez disso, vamos usar um login fixo, o qual pode ser facilmente modificado pelo leitor ao final deste artigo.

Mas antes disso, precisamos assegurar que o ambiente esteja devidamente configurado. Para os objetivos deste artigo é preciso que o leitor tenha as respectivas ferramentas informadas na seção anterior configuradas, não mostraremos aqui os passos para não perder o foco, mas podemos encontrar facilmente os passos no site oficial de cada uma. Com tudo ok, abra o seu Eclipse, selecione um workspace e vamos criar um projeto web, já que nosso Web Services precisa estar hospedado no formato web em algum servidor (certifique-se de importar o servidor do Tomcat para o Eclipse também, no formato zip – basta descompactar).

Para isso, vá até o menu File > New > Dynamic Web Project e, uma vez com o Tomcat já importado no ambiente, dê um nome ao projeto (devmedia_news_rest_web), selecionando as opções a seguir, tal como mostra a Figura 2:

  • Target Runtime: Apache Tomcat v7.0;
  • Dynamic web module version: 3.0;
  • Configuration: Default Configuration for Apache Tomcat v7.0.
Wizard de criação de projeto web no Eclipse
Figura 2. Wizard de criação de projeto web no Eclipse.

Clique em Next duas vezes e na terceira tela marque a checkbox Generate web.xml deployment descriptor, finalizando a configuração. Após isso, precisamos converter o nosso projeto para um projeto de tipo Maven (ferramenta de gerenciamento de dependências, bibliotecas e build de projetos) para, assim, não termos de nos preocupar com a manutenção das libs do projeto de forma manual. Portanto, clique com o botão direito sobre o projeto e selecione a opção Configure > Convert to Maven Project e pronto. Isso será o bastante para que um novo arquivo pom.xml seja adicionado à raiz do projeto, esse é o arquivo que usaremos para inserir nossas dependências.

Dê um duplo click no mesmo e uma interface gráfica será aberta, com várias abas na parte inferior da IDE. Selecione a aba pom.xml (a última) e insira o conteúdo apresentado na Listagem 1.


<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
  http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
  <groupId>br.com.edu.devmedia.news_rest</groupId>
  <artifactId>devmedia_news_rest_web</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <build>
   <sourceDirectory>src</sourceDirectory>
   <plugins>
    <plugin>
     <artifactId>maven-compiler-plugin</artifactId>
     <version>3.3</version>
     <configuration>
           <source>1.8</source>
           <target>1.8</target>
     </configuration>
    </plugin>
    <plugin>
     <artifactId>maven-war-plugin</artifactId>
     <version>2.6</version>
     <configuration>
           <warSourceDirectory>WebContent</warSourceDirectory>
            <failOnMissingWebXml>false</failOnMissingWebXml>
     </configuration>
    </plugin>
   </plugins>
  </build>
  <dependencies>
   <dependency>
          <groupId>org.glassfish.jersey.containers</groupId>
          <artifactId>jersey-container-servlet</artifactId>
          <version>2.22.1</version>
   </dependency>
   <!-- Required only when you are using JAX-RS Client -->
   <dependency>
          <groupId>org.glassfish.jersey.core</groupId>
          <artifactId>jersey-client</artifactId>
          <version>2.22.1</version>
   </dependency>

   <dependency>
          <groupId>org.glassfish.jersey.media</groupId>
          <artifactId>jersey-media-moxy</artifactId>
          <version>2.22.1</version>
   </dependency>
  </dependencies>
</project>
Listagem 1. Conteúdo do arquivo de configuração pom.xml

Trata-se de um arquivo simples que traz apenas algumas configurações a nível de projeto e build, bem com as três dependências que precisaremos para criar o nosso serviço. Nas linhas 4 e 5 temos os ids de grupo e artefato do projeto, respectivamente, usados pelo Maven para gerir a forma como nosso projeto será exportado no futuro. Na linha 7 definimos que queremos exportá-lo como um projeto web, em formato war (web archive). Na linha 9 definimos em que diretório estarão nossas classes de código fonte, e da linha 10 a 27 configuramos dois plugins necessários ao projeto: o primeiro para definir o nível do compilador para o projeto (1.8) e o segundo para definir a versão de módulo web que estaremos trabalhando (2.6, mas podemos usar qualquer outra mais recente até a 3.1). Por fim, nas linhas 29 a 47 definimos três dependências: as APIs do Jersey para Restful servlet e cliente, e a API do Jersey Media Moxy, que nos auxiliará a enviar e receber objetos no lado JavaScript em formato JSON quando chegarmos na parte do AngularJS.

O Jersey é uma API do Java para criar Web Services de forma fácil e rápida, com poucas configurações e totalmente baseados em annotations. É a escolha ideal para o tipo de projeto que estamos criando, pois simplificará tudo no final, além de ser muito semelhante à forma como o AngularJS mapeia as coisas no lado cliente.

Após copiar o conteúdo salve o arquivo e aguarde até que o Maven baixe todas as dependências necessárias (precisaremos de uma conexão com a internet sem proxy para isso). Para conferir se o procedimento todo funcionou, vá até o menu Properties > Java Build Path > Libraries > Maven Dependencies e veja todas as libs baixadas.

O próximo passo consiste em configurar o Jersey para que seja reconhecido pelo projeto. Para isso, vá até a pasta Java Resources > src e clique com o botão direito, selecione a opção New > Package e dê o nome br.edu.devmedia.entidade. Clique em Finish. Logo após, abra o arquivo web.xml que está dentro do diretório WebContent > WEB-INF e adicione o conteúdo da Listagem 2 ao mesmo.


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
   http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  id="WebApp_ID" version="3.0">
  <display-name>devmedia_news_rest_web</display-name>
  <welcome-file-list>
   <welcome-file>index.html</welcome-file>
  </welcome-file-list>

  <servlet>
   <servlet-name>Jersey REST Service</servlet-name>
   <servlet-class>org.glassfish
   .jersey.servlet.ServletContainer</servlet-class>
   <init-param>
          <param-name>jersey.config.server
          .provider.packages</param-name>
          <param-value>br.edu.devmedia.rest</param-value>
   </init-param>
   <init-param>
          <param-name>com.sun.jersey.api.json
          .POJOMappingFeature</param-name>
          <param-value>true</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
   <servlet-name>Jersey REST Service</servlet-name>
   <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
</web-app>
Listagem 2. Conteúdo do arquivo de configurações web.xml

Essas são as configurações do Servlet do Jersey que precisamos mapear para informar ao contexto da aplicação web quem se encarregará de receber as requisições do AngularJS e quem devolverá as respectivas respostas. Trata-se da classe ServletContainer. Perceba que dentro dessa configuração estamos passando também um parâmetro de inicialização chamado jersey.config.server.provider.packages que recebe o exato nome do pacote que acabamos de criar. Ao fazer isso, estamos dizendo ao Jersey que todas as classes de Web Services estarão contidas dentro deste pacote, por isso não devemos anotar nenhuma outra classe externa ao mesmo como receptora de Web Services.

O segundo parâmetro de inicialização (linha 18) serve para dizer ao mesmo que trabalharemos com JSON e que o mesmo deve fazer a conversão automática de objetos para JSON e vice-versa. No final do arquivo, na linha 24, configuramos o padrão de URLs que usaremos para redirecionar para esse Servlet, com o valor /rest/*. Isso significa que somente as requisições que tiverem esse padrão na URL serão aceitas por nosso serviço. Pronto, nosso projeto está configurado para receber nossas classes de serviços.

Como nosso serviço só tratará de requisições para login no momento, vamos criar uma nova classe e pacote de entidades para receber os objetos que precisaremos converter do JavaScript para o Java. O primeiro deles será o objeto Usuario que conterá as informações de login e senha. Portanto, vá até a pasta src e clique com o botão direito selecionando a opção. No campo Package informe o valor br.edu.devmedia.entidade e no campo Name informe Usuario. Quando for criado e aberto, adicione o conteúdo da Listagem 3 ao mesmo. Nessa listagem a única novidade é a anotação @XmlRootElement que pertence à API do Jersey e serve para fazer a conversão automática para objeto Java quando esse objeto vier do AngularJS em formato JSON. Além disso, criamos um atributo booleano logado para mapear se o usuário está ou não logado no sistema no momento.


package br.edu.devmedia.entidade;
 
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement
public class Usuario {
 
     private String usuario;
     private String senha;
 
     private boolean logado;
     
     public String getUsuario() {
       return usuario;
     }
 
     public void setUsuario(String usuario) {
       this.usuario = usuario;
     }
 
     public String getSenha() {
       return senha;
     }
 
     public void setSenha(String senha) {
       this.senha = senha;
     }
 
     public boolean isLogado() {
       return logado;
     }
 
     public void setLogado(boolean logado) {
       this.logado = logado;
     }
 
}
Listagem 3. Código da classe de entidade Usuario

O próximo passo é criar a nossa classe de serviço, de fato. Vá até o pacote de terminação .rest que criamos, clique com o botão direito e selecione New > Class, dê o nome LoginService à mesma e clique em Finish. Adicione o conteúdo da Listagem 4.


package br.edu.devmedia.rest;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import br.edu.devmedia.entidade.Usuario;

@Path("/login")
public class LoginService {

  @POST
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  public Usuario logarUsuario(Usuario usuario) {
   if ("admin".equals(usuario.getUsuario())) {
          usuario.setLogado(true);
   } else {
          usuario.setLogado(false);
   }

   return usuario;
  }
}
Listagem 4. Código da classe de serviço LoginService

Nessa classe já começamos a ver as anotações do Jersey aparecerem com maior intensidade. A primeira delas, na linha 11, é a @Path que é responsável por mapear qual URI (URL interna) deverá ser chamada na URL principal para que a requisição seja direcionada para a classe em questão. No método logarUsuario() representado na linha 17 estamos mapeando-o com três anotações: @POST se encarrega de definir o tipo de método HTTP que esse método Java receberá (o que significa que outros métodos não serão aceitos, como GET ou PUT); @Produces define que tipo de conteúdo esse método retornará para o cliente (no caso JSON); e a @Consumes define o que ele consumirá, também conteúdo em formato JSON.

Note que nossa validação focará apenas em validar se o nome de usuário que recebemos por parâmetro será igual a admin, e retornar o mesmo objeto de usuário preenchido com tal informação na flag logado. Esse é todo o código back-end que precisamos para fazer o login funcionar. Para testar se o mesmo está funcionando, basta adicionar o projeto no servidor Tomcat, iniciar o mesmo e executar a URL em uma ferramenta de testes do tipo, como o SoapUI, por exemplo.

Criando o cliente da aplicação

Trabalhar com o AngularJS é demasiadamente simples e para evitar aumento de complexidade no projeto vamos importar todos os arquivos de script usando o recurso de CDN (Content Delivery Network). Este nos fornece os arquivos em um servidor escalável em tempo real a qualquer momento (na página oficial do framework - seção Links). Para isso, a primeira providência é criar a nossa página HTML (sim, com o AngularJS podemos executar tudo em HTML sem a necessidade de extensões server side, como .jsp, .php, etc.). Vá até a pasta WebContent, clique com o botão direito e selecione New > HTML File. Dê um nome (index.html) ao arquivo e clique em Finish. Adicione o conteúdo da Listagem 5 ao mesmo.


<html ng-app="app">
   <head>
       <title>Painel Administrativo - Login</title>
       <meta charset="utf-8">
       
       <link rel="stylesheet" href="http://getbootstrap.com/dist/css/bootstrap.min.css" />
   </head>
   <body>
        <div ng-controller="loginController">       
         <div class="container">
           <div class="row">
               <div class="col-xs-12">
                   <div class="page-header">
                       <h3>Login do Portal</h3>
                   </div>
               </div>
           </div>
           
           <div class="row">
               <div class="col-xs-12">
                   <form class="form-horizontal" 
                   ng-submit="efetuarLogin()">
                     <div class="form-group">
                       <label for="inputEmail3" 
                       class="col-sm-2 control-label"
                       >E-mail</label>
                       <div class="col-sm-10">
                         <input type="text" 
                         class="form-control" id="inputEmail3" 
                         placeholder="Informe o seu e-mail" 
                         ng-model="login.usuario" required>
                       </div>
                     </div>
                     <div class="form-group">
                       <label for="inputPassword3" 
                       class="col-sm-2 control-label">Senha</label>
                       <div class="col-sm-10">
                         <input type="password" 
                         class="form-control" id="inputPassword3" 
                         placeholder="Informe a sua senha" 
                         ng-model="login.senha" required>
                       </div>
                     </div>
                     
                     <div class="form-group">
                       <div class="col-sm-offset-2 col-sm-10">
                         <button type="submit" 
                         class="btn btn-default">Logar</button>
                       </div>
                     </div>
                   </form>
               </div>
           </div>
        </div>

        <!-- Modal -->
        <div id="modal-validacao" class="modal fade" 
        role="dialog">
        <div class="modal-dialog">

          <!-- Modal content-->
          <div class="modal-content">
            <div class="modal-header">
              <button type="button" class="close" 
              data-dismiss="modal">×</button>
              <h4 class="modal-title">Alerta!</h4>
            </div>
            <div class="modal-body">
              <p>{{txtModal}}</p>
            </div>
            <div class="modal-footer">
              <button type="button" class="btn btn-default" 
              data-dismiss="modal">Fechar</button>
            </div>
          </div>
        </div>
        </div>
        </div>
       
       <script src="https://ajax.googleapis.com/ajax/libs/
       angularjs/1.4.5/angular.min.js"></script>
       <script src="js/app.module.js"></script>
       <script src="js/loginController.js"></script>
       <script src="https://ajax.googleapis.com/ajax/libs/jquery/
       1.11.3/jquery.min.js"></script>
      <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/
      bootstrap.min.js"></script>
   </body>
</html>
Listagem 5. Página index.html para login de usuário

Vejamos alguns detalhes acerca dessa listagem:

  • Logo na primeira linha do código temos um atributo novo sendo exibido na tag <html>, trata-se do ng-app. Esse atributo, pertencente ao AngularJS, se encarrega de configurar uma aplicação Angular na nossa página. O framework trabalha com essa divisão de responsabilidades via aplicações, onde em um só projeto podemos ter várias dessas. Entretanto, cada uma funciona como um módulo do sistema e, por isso, precisamos nos certificar de mapeá-los corretamente no JavaScript (veremos mais adiante);
  • Na linha 6 importamos o arquivo de estilo CSS do Bootstrap, que usaremos para incutir estilo na aplicação, sem ter de criar o nosso do zero;
  • Na linha 9 criamos o nosso controller do AngularJS (via atributo ng-controller) de nome loginController. Este será responsável por nos prover os métodos e objetos HTTP e de escopo da página, o qual, consequentemente, retornará os objetos dinâmicos e nos permitirá mapear valores e recuperar os mesmos dinamicamente via JavaScript;
  • Das linhas 11 a 16 criamos o cabeçalho da página usando as classes CSS do Bootstrap;
  • Das linhas 19 a 42 criamos o formulário de autenticação da aplicação. Veja que na linha 21 implementamos nosso form com o atributo ng-submit que se encarrega de informar qual função JavaScript do AngularJS será responsável por lidar com o envio do formulário. No AngularJS usamos o conceito de modelos para mapear os campos e valores referentes a uma entidade via JavaScript. Para isso, precisamos mapear cada campo de formulário (input, select, etc.) com a propriedade ng-model, passando o objeto e o atributo interno, como faríamos em uma aplicação OO convencional (linhas 25 e 31);
  • Das linhas 46 a 64 criamos o HTML de uma modal usando o estilo do Bootstrap, bem como suas classes. Veja que dessa vez usamos alguns atributos novos, como o role ou data-dismiss, mas estes pertencem ao framework do Bootstrap e não ao AngularJS. A grande novidade dessa parte está no atributo do AngularJS da linha 53, o txtModal. Ele está entre chaves duplas {{}} em detrimento da exigência do framework em mapear variáveis dessa forma. Assim, quando precisarmos enviar qualquer mensagem de notificação para a página basta acessar o controller passando tal valor;
  • Finalmente, nas linhas 66 a 70 efetuamos os imports dos arquivos de script dos frameworks do AngularJS (na sua versão 1.4.5, a mais recente até o momento da publicação), jQuery (versão 1.11.3), Bootstrap (versão 3.3.5), além dos scritps de aplicação e controller que criaremos a seguir.

Agora que criamos o HTML inicial, estamos aptos a entender como o AngularJS trabalha a nível de JavaScript para validar o formulário e enviar a requisição para o nosso Web Service no lado do servidor. O primeiro passo é criar o novo arquivo app.module.js dentro da pasta WebContent/js (se ela não existir, criar). Insira o código a seguir no mesmo:


var app = angular.module('app', []);

Esse código é bem conhecido, pois trata-se de uma implementação simplista da criação de um novo módulo de aplicação no AngularJS. A variável deve ser criada sempre fora de qualquer bloco de inicialização para permitir que sua visualização seja feita de forma global. O segundo parâmetro do módulo é um vetor com os dados de inicialização (opcionais).

Após isso, crie também um segundo arquivo de nome loginController.js dentro do mesmo diretório e adicione o conteúdo da Listagem 6 ao mesmo.


app.controller('loginController', function($scope, $http) {

   $scope.login = {
          usuario : "",
          senha : ""
   };

   $scope.efetuarLogin = function() {
          if ($scope.login.usuario == "" || $scope.login.senha == "") {
                 alert("Informe usuário e senha!");
                 return;
          }

          $http.post('/devmedia_news_rest_web/rest/login', 
          $scope.login).success(function(data) {
                 console.log(data);

                 if (data.logado) {
                       window.location = "painel-inicial.html"
                 } else {
                       $("#modal-validacao").modal("show");
                       $scope.txtModal = "Usuário/Senha inválidos!";
                 }
          });
   }
});
Listagem 6. Conteúdo JavaScript do controller de login

Na primeira linha temos a chamada à função controller() do AngularJS, que basicamente mapeia um novo controller ao já existente módulo app (criado na listagem anterior), passando o nome como primeiro argumento, e uma função anônima como segundo parâmetro. Esta função recebe dois argumentos também: o $scope que se refere ao objeto de escopo do módulo, isto é, tudo que estiver dentro da div que mapeamos como o controller estará também disponível neste objeto via JavaScript; e o $http, que é um objeto implícito para efetuar operações via HTTP (GET, POST, etc.).

Dentro do objeto de escopo temos também o model que criamos (login) na HTML. Dentro dele precisamos definir quais serão os atributos (usuario e senha), resetando-os. Após isso, na linha 8, mapeamos no mesmo escopo a função que efetuará o login de fato. Nela, fazemos primeiro uma validação simples para ver se os campos foram preenchidos, exibindo uma mensagem de validação (via alert) caso contrário. Aparentemente esse código parece desnecessário, já que definimos os campos na tela como required que, nos browsers mais recentes, obrigam o preenchimento dos mesmos. Mas lembre-se que o usuário pode estar acessando sua aplicação de um browser antigo, sem suporte ao HTML5, além de poder perfeitamente inspecionar os mesmos campos e remover tal atributo, fazendo com que o campo não seja mais obrigatório. Portanto, sempre garanta as validações em mais de uma via, para permitir que a aplicação esteja o mais segura possível.

Em seguida, na linha 14 chamamos a função post() no objeto $http que é bem semelhante à que temos no jQuery. O primeiro argumento se refere à URL de acesso ao serviço que está hospedado na aplicação web, via Jersey. Para esse tipo de implementação, considerando-se que já estamos dentro do mesmo escopo de aplicação, não precisamos referenciar a URL completa (como em http://localhost:8080/devmedia_news_rest_web/rest/login), apenas a URI interna relativa ao projeto. Como segundo parâmetro, enviamos o objeto de login que está no escopo do AngularJS, já preenchido automaticamente pelo próprio framework. Esse binding de informações é padrão no AngularJS, portanto, não precisamos nos preocupar em como ele o realiza.

Logo depois, chamamos a função success() que se encarrega de executar o código de sucesso do retorno dessa requisição. Nessa mesma função, recebemos um objeto data que corresponde ao response do request. Nele teremos os dados de retorno do Web Service que, como vimos, estará no formato JSON. O JavaScript já tem suporte nativa a esse tipo de formato de dados, logo, não teremos problema quanto ao acesso e conversão dos nossos. Na linha 15 logamos as informações no Console do navegador, apenas para averiguar se a resposta retornou com sucesso. Na linha 17 testamos se a informação do objeto retornou que o usuário está logado e navegamos para a próxima página de boas-vindas, caso positivo. Caso contrário, mapeamos o objeto de modal (linha 20) e setamos o valor da variável do AngularJS txtModal para uma mensagem de usuário e senha inválidos. Pronto, esse é todo o código fonte que precisamos no lado JavaScript para fazer o exemplo funcionar.

Agora só precisamos criar a página de boas-vindas para ter para onde navegar quando o exemplo finalizar. Para isso, vá até o diretório WebContent/js novamente e crie uma nova página HTML de nome painel-inicial.html e adicione o conteúdo da Listagem 7 à mesma. Nela não temos muitas novidades, exceto pelo import do Bootstrap e de um título provisório de cabeçalho.


<html ng-app="app">
<head>
    <title>Home</title>
    <meta charset="utf-8">
    
    <link rel="stylesheet" 
    href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/
    css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-xs-12">
            
                <div class="page-header">
                    <h3>Usuário autenticado com sucesso!</h3>
                </div>
                
            </div>
        </div>
    </div>
</body>
</html>
Listagem 7. Conteúdo da página HTML de boas-vindas

Para testar tudo, suba a aplicação web no servidor, inicie-o e execute a seguinte URL no browser: http://localhost:8080/devmedia_news_rest_web/. O resultado pode ser conferido na Figura 3.

Tela de login da nossa aplicação
Figura 3. Tela de login da nossa aplicação.

Também é aconselhado que o leitor habilite a ferramenta do desenvolvedor no browser para analisar se tudo foi carregado corretamente a nível de JavaScript. Se nenhum erro aparecer nossas configurações estão ok. Lembre-se que fixamos a validação do usuário para o valor admin, logo, para testar precisamos informar esse usuário seguido de uma senha qualquer.

Preencha os dados do formulário e clique em Logar para ver uma tela igual à da Figura 4 (caso de login com sucesso).

Tela de boas-vindas (logado com sucesso)
Figura 4. Tela de boas-vindas (logado com sucesso).

Caso a autenticação falhe, por exemplo, informar um usuário inválido, teremos o mesmo resultado demonstrado na Figura 5.

Modal com mensagem de notificação
Figura 5. Modal com mensagem de notificação.

Cadastrando novas notícias

Agora que nosso login está funcional podemos focar na implementação do cadastro de novas notícias na base. Vamos começar então preparando o terreno no lado do servidor, especificamente criando a nova entidade Noticia que conterá os atributos e métodos de acesso às propriedades do banco finais. Para isso, crie uma nova classe de nome Noticia, no pacote de entidade, e acrescente o conteúdo da Listagem 8 à mesma.


cpackage br.edu.devmedia.entidade;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Noticia {

     private int id;
     private String titulo;
     private String descricao;
     private String data;
     private String texto;

     // Get's e set's omitidos
}
Listagem 8. Nova entidade Noticia

A classe não traz nenhuma novidade e todos os atributos foram mapeados como sendo do tipo String. Mesmo a data estando no formato Timestamp no banco de dados, faremos a conversão na hora de salvar na base. Veja também que usamos mais uma vez a anotação @XmlRootElement para fazer a conversão do valor na hora de enviar via JSON.

No mesmo pacote, crie um novo enum para guardar os status da notícia, conforme desenhamos no banco também. Acrescente o seguinte conteúdo ao mesmo:


public enum Status {
       ATIVA, INATIVA
}

Esses valores serão úteis quando precisarmos salvar uma nova notícia na base. Para finalizar o lado servidor crie uma nova classe de serviço, no pacote br.edu.devmedia.rest, de nome NoticiaService e acrescente o conteúdo demonstrado na Listagem 9.


package br.edu.devmedia.rest;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;

import br.edu.devmedia.config.DatabaseConfig;
import br.edu.devmedia.entidade.Noticia;
import br.edu.devmedia.entidade.Status;

@Path("noticia")
public class NoticiaService {

   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   @Path("/new")
   public void logarUsuario(Noticia noticia) {
          Connection con = DatabaseConfig.getConnection();
          
          DateFormat format = new SimpleDateFormat("dd/MM/yyyy");
          
          try {
                 PreparedStatement stm = 
                 con.prepareStatement("INSERT INTO TB_NOTICIA(TITULO, 
                 DESCRICAO, TEXTO, DATA, STATUS) VALUES(?, ?, ?, ?, ?)");
                 stm.setString(1, noticia.getTitulo());
                 stm.setString(2, noticia.getDescricao());
                 stm.setString(3, noticia.getTexto());
                 
                 Date data = new Date(format
                 .parse(noticia.getData()).getTime());
                 
                 stm.setDate(4, data);
                 stm.setInt(5, Status.ATIVA.ordinal());
                 
                 stm.execute();
                 con.close();
          } catch (SQLException | ParseException e) {
                 e.printStackTrace();
          }
   }
}
Listagem 9. Classe de serviço para as notícias

Na linha 20 da listagem temos as configurações de caminho (@Path) que fizemos no mapeamento anterior. Dessa vez, especificamente no método logarUsuario(), também estamos usando a mesma anotação, uma vez que teremos mais de uma método na classe de serviço. Quando isso acontece, precisamos acessar o caminho completo acessando a URL dessa forma: localhost:8080/aplicação/noticia/new. O restante do procedimento de receber o parâmetro é igual ao que já fizemos.

Em seguida, a partir da linha 27, começamos as configurações de acesso ao banco de dados MySQL. Aqui, usaremos o JDBC do Java, portanto, duas configurações iniciais se fazem necessárias:

  1. Acrescentar a dependências do conector do MySQL para o Java no pom.xml, como mostra a Listagem 10;
  2. Criar uma classe de conexão com o banco que criamos antes, retornando objetos do tipo java.sql.Connection, como mostra a Listagem 11.

<dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.37</version>
</dependency>
Listagem 10. Código XML de adição da lib do MySQL Conector

package br.edu.devmedia.config;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseConfig {

   public static Connection getConnection() {
         try {
                Class.forName("com.mysql.jdbc.Driver");
                return DriverManager.getConnection
                ("jdbc:mysql://localhost:3306/news_devmedia", 
                "root", "root");
         } catch (SQLException | ClassNotFoundException e) {
                e.printStackTrace();
         }
         return null;
   }
}
Listagem 11. Classe de conexão com o banco

Nessa classe, basicamente passamos a URL de conexão (que termina com o nome do nosso banco criado), bem como o usuário e senha do MySQL (certifique-se de substituir por suas credenciais).

Voltando à Listagem 9, na linha 29 criarmos um objeto de formatação de datas, que se encarregará de converter a data recebida em formato String para java.sql.Date (reconhecível pelo banco). O restante do código apenas insere uma nova linha na tabela TB_NOTICIA, fazendo uso do enum de Status que criamos para setar a notícia com ativa. Note que o método não retorna nada, logo, teremos de lidar com a regra de sucesso/erro no AngularJS.

No lado cliente, comecemos pela edição do arquivo de painel-inicial.html. Edite-o com as informações da Listagem 12.


<!DOCTYPE html>
<html ng-app="app">
   <head>
       <title>Painel Administrativo</title>
       <meta charset="utf-8">
       <link rel="stylesheet" href="http://getbootstrap.com/
       dist/css/bootstrap.min.css" />
       <style>
                 .mbottom {
                       margin-bottom: 10px;
                 }
          </style>
   </head>
   <body>
       <nav class="navbar navbar-default">
         <div class="container-fluid">
           <div class="navbar-header">
             <a class="navbar-brand" href="#">Dashboard</a>
           </div>
         </div>
       </nav>
       
       <div ng-controller="painelInicialController">
           <div class="container">
               <div class="row">
                   <div class="col-xs-12">
                       <div class="well well-sm">
                           <button type="button" 
                           class="btn btn-primary" 
                           ng-click="abreCadastroNoticia()"
                           >Nova Notícia</button>
                       </div>
                   </div>
               </div>
           </div>
           
           <div class="container" ng-show="showCadastro">
               <form ng-submit="cadastrarNovaNoticia()">
                   
                   <div class="row mbottom">
                       <div class="col-xs-3 text-right">
                           Título:
                       </div>
                       <div class="col-xs-9">
                           <input type="text" class="form-control" 
                           ng-model="noticia.titulo" required>
                       </div>
                   </div>
                   
                   <div class="row mbottom">
                       <div class="col-xs-3 text-right"
                       >Descrição:</div>
                       <div class="col-xs-9">
                           <input type="text" class="form-control" 
                           ng-model="noticia.descricao" >
                       </div>
                   </div>
                   
                   <div class="row mbottom">
                       <div class="col-xs-3 text-right">Data:</div>
                       <div class="col-xs-9">
                           <input class="form-control" ng-model="noticia.data" 
                           ui-mask="99/99/9999" model-view-value="true">
                       </div>
                   </div>
                   
                   <div class="row mbottom">
                       <div class="col-xs-3 text-right"
                       >Texto:</div>
                       <div class="col-xs-9">
                           <textarea class="form-control" 
                           ng-model="noticia.texto" rows="5">
                           </textarea>
                       </div>
                   </div>
                   
                   <div class="row mbottom">
                       <div class="col-xs-9 col-xs-offset-3">
                           <button class="btn btn-danger" 
                           type="submit">Inserir</button>
                       </div>
                   </div>
                   
               </form>
           </div>
       </div>
       
       <script src="https://ajax.googleapis.com/ajax/
       libs/angularjs/1.4.5/angular.min.js"></script>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/
       angular-ui-utils/0.1.1/angular-ui-utils.min.js"></script>
       <script src="js/painelInicialController.js"></script>
   </body>
</html>
Listagem 12. Novo conteúdo da página painel-inicial.html

Exceto pelas novas configurações do AngularJS (app + controller) e organização da estrutura HTML via classes e divs do Bootstrap, a listagem não traz muitas novidades. Vejamos algumas observações:

  • Na linha 2 declaramos a mesma aplicação AngularJS;
  • Na linha 22 temos a definição do novo controller painelInicialController, que se encarregará de conter todo o escopo da nova página;
  • Na linha 27 definimos o botão de adição de novas notícias com sua respectiva função AngularJS abreCadastroNoticia(). Essa função apenas exibe a div com o formulário de cadastro;
  • Na linha 33 criamos o novo formulário com a diretiva ng-show, que serve para definir qual função JavaScript exibe ou esconde o mesmo;
  • Na linha 34 definimos a nova função de submit do formulário;
  • Dentro do formulário definimos os campos com os respectivos ng-model configurados para o modelo noticia;
  • Na linha 55 criamos o campo de data que recebe um novo atributo: o ui-mask. Trata-se de um módulo que adicionaremos do AngularJS para mascarar campos via JavaScript de forma simplificada através de padrões;
  • Finalmente, nas linhas 76 a 78 importamos via CDN os arquivos de JavaScript do AngularJS, do módulo UI-Utils do AngularJS e do painelController.js, que criaremos a seguir.

Em seguida, crie um novo arquivo JavaScript de nome painelController.js na pasta js, e adicione o conteúdo da Listagem 13.


var app = angular.module('app', [ 'ui.mask' ]);

app.controller('painelInicialController', function($scope, $http) {
 $scope.showCadastro = false;
 $scope.noticia = montarObjNoticia();

 $scope.abreCadastroNoticia = function() {
        $scope.showCadastro = true;
 }

 $scope.cadastrarNovaNoticia = function() {
        var string = $scope.noticia.data;
        var month = string.substring(0,2);
        var day = string.substring(2,4);
        var year = string.substring(4,8);
        var data_final = month + '/' + day + '/' + year;
        
        $scope.noticia.data = data_final;
        
        $http.post('/devmedia_news_rest_web/rest/noticia/new', $scope.noticia)
               .success(function(data) {
                     alert("Cadastro efetuado com sucesso!");
                     $scope.showCadastro = false;
                     $scope.noticia = montarObjNoticia();
               }).error(function() {
                     alert("Falha ao cadastrar notícia!");
               });
 };
});

function montarObjNoticia() {
 return {
        id : -1,
        titulo : "",
        descricao : "",
        texto : "",
        data : ""
 };
}
Listagem 13. Conteúdo do arquivo painelController.js

Veja que na primeira linha já temos a primeira mudança significativa no uso da função module() do AngularJS. Aqui sobrescrevemos o objeto global app para receber agora o módulo e carregar também a dependências da biblioteca ui.mask. Dessa forma, garantimos que o código a seguir a reconhecerá. Vejamos algumas considerações:

  • Na linha 3 criamos o controller propriamente dito, com os mesmos objetos de antes;
  • Na linha 4 escondemos a div de cadastro, via JavaScript, para exibi-la quando do click no botão de Adicionar Nova Notícia;
  • Na linha 5 instanciamos um novo objeto de notícia, através da função auxiliar montarObjNoticia() da linha 31, com valores padrão vazios;
  • Na linha 11 criamos a função de cadastro da nova notícia que:

o Recebe a data do formulário e a quebra em dia, mês e ano, para assim enviar o valor formatado, já que o AngularJS retorna só o texto;

o Efetue a requisição post ao novo serviço, passando o objeto de notícia como argumento;

o Manipula as funções de sucesso e erro, exibindo mensagens específicas para cada uma e limpando o objeto de notícia para um novo cadastro.

Agora basta reiniciar o servidor Tomcat e retestar a aplicação. O resultado pode ser conferido nas Figuras 6 e 7.

Tela de cadastro com dados de exemplo
Figura 6. Tela de cadastro com dados de exemplo.
Mensagem de cadastro com sucesso
Figura 7. Mensagem de cadastro com sucesso.

Listando notícias

Para efetuar a listagem, vamos fazer o mesmo procedimento no Java, porém agora retornando JSON, em vez de consumindo. Para isso, crie um novo método no nosso NoticiaService, como mostra a Listagem 14.


@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/listar")
public List<Noticia> listarNoticias() {
   List<Noticia> noticias = new ArrayList<Noticia>();

   Connection con = DatabaseConfig.getConnection();

   try {
         PreparedStatement stm = con.prepareStatement("SELECT * FROM TB_NOTICIA");
         ResultSet rs = stm.executeQuery();
         while (rs.next()) {
                Noticia noticia = new Noticia();
                noticia.setId(rs.getInt("id"));
                noticia.setTitulo(rs.getString("titulo"));
                noticia.setDescricao(rs.getString("descricao"));
                noticia.setTexto(rs.getString("texto"));
                noticia.setData(rs.getDate("data").toString());
                
                noticias.add(noticia);
         }
         con.close();
   } catch (SQLException e) {
         e.printStackTrace();
   }
   return noticias;
}
Listagem 14. Novo método de listagem de notícias

Este pode ser um GET, em vista de não precisarmos enviar nenhum parâmetro e o mesmo ser mais rápido. A lógica dele se encarrega agora de explicitamente criar uma lista Java e retorná-la preenchida com os valores de todas as notícias cadastradas na base. Agora só precisamos atualizar a nossa HTML e JS para receber as mudanças, incluindo agora um novo componente de tabela. Veja na Listagem 15 as alterações necessárias ao HTML.


<div class="container">
  <div class="row">
     <div class="col-xs-12">
     
      <table class="table table-bordered table-striped table-hover">
       <thead>
          <tr>
            <th width="90">Data</th>
            <th>Titulo</th>
            <th>Descrição</th>
          </tr>
       </thead>
       
       <tbody>
              
          <tr ng-repeat="noticia in allNoticias">
            <td>{{ noticia.data }}</td>
            <td>{{ noticia.titulo }}</td>
            <td>{{ noticia.descricao }}</td>
          </tr>
              
       </tbody>
       
      </table>
            
     </div>
   </div>
</div>
Listagem 15. Alterações na painel-inicial.html

Veja que agora estamos usando uma tag nova do AngularJS, a ng-repeat, que serve para iterar sobre um array/lista de valores mandados juntos do escopo. Funciona como uma estrutura forEach de linguagens mais famosas, como o Java ou C#. Na Listagem 16 encontramos as alterações necessárias ao arquivo painelController.js para fazer o exemplo funcionar. Acrescente as mesmas dentro da declaração da função de controller().


$scope.allNoticias = {};

$scope.listarNoticias = function(){
 $http.get('/devmedia_news_rest_web/rest/noticia/listar')
       .success(function(data){
              $scope.allNoticias = data;
       })
       .error(function(){
              alert("Falha em obter as notícias");
       });
};

$scope.listarNoticias();
Listagem 16. Alterações no painelController.js

Veja que agora usamos a função get(), ao invés de post(), com o mesmo significado. O resto é só associação e chamadas de métodos. Reinicie o servidor e veja o resultado final, semelhante à Figura 8.

Resultado da listagem de notícias
Figura 8. Resultado da listagem de notícias.

Essas foram somente algumas configurações e implementações possíveis junto com o AngularJS. Podemos ainda integrar tudo a outros frameworks, como vimos com o jQuery e o Bootstrap, ou incluir suas próprias bibliotecas junto. Essa é uma das grandes vantagens de usar o AngularJS, sua flexibilidade e integração.


Links Úteis


Saiba mais sobre JavaScript ;)

  • JavaScript Tutorial: Neste artigo veremos como utilizar o código javascript em nossas aplicações web e websites.
  • Guias de referência JavaScript: JavaScript é uma linguagem de programação amplamente utilizada no desenvolvimento web, tanto no front-end, em conjunto com HTML e CSS, quanto no back-end, com Node.js.
  • Trabalhando com eventos em JavaScript: Veja nesse artigo como trabalhar com alguns tipos de eventos na linguagem JavaScript.