Construindo um aplicativo Web com Spring Boot e Angular
1. Visão geral
Spring BooteAngular formam um tandem poderoso que funciona muito bem para desenvolver aplicativos da web com um mínimo de pegada.
Neste tutorial,we’ll use Spring Boot for implementing a RESTful backend, and Angular for creating a JavaScript-based frontend.
Leitura adicional:
Controladores acionados por interface na primavera
Aprenda a criar controladores usando a anotação de solicitação do Spring MVC nas interfaces Java.
API REST do Spring + OAuth2 + Angular
Aprenda a configurar o OAuth2 para uma API REST Spring e como consumi-lo em um cliente Angular.
2. O aplicativo Spring Boot
A funcionalidade de nosso aplicativo de demonstração da web será bastante simplista. Será apenas reduzido para buscar e exibirList de entidades JPA de umH2 database na memória e persistir novos por meio de um formulário HTML simples.
2.1. As dependências do Maven
Aqui estão as dependências do nosso projeto Spring Boot:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
com.h2database
h2
runtime
Observe que incluímosspring-boot-starter-web porque vamos usá-lo para criar o serviço REST espring-boot-starter-jpa para implementar a camada de persistência.
A versãoH2 database também é gerenciada pelo pai Spring Boot.
2.2. A classe de entidade JPA
Para criar um protótipo rápido da camada de domínio de nosso aplicativo, vamos definir uma classe de entidade JPA simples, que será responsável por modelar usuários:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private final String name;
private final String email;
// standard constructors / setters / getters / toString
}
2.3. A interfaceUserRepository
Como precisaremos da funcionalidade CRUD básica nas entidadesUser, também devemos definir uma interfaceUserRepository:
@Repository
public interface UserRepository extends CrudRepository{}
2.4. O controlador REST
Agora, vamos implementar a API REST. Neste caso, é apenas um controlador REST simples.
@RestController
@CrossOrigin(origins = "http://localhost:4200")
public class UserController {
// standard constructors
private final UserRepository userRepository;
@GetMapping("/users")
public List getUsers() {
return (List) userRepository.findAll();
}
@PostMapping("/users")
void addUser(@RequestBody User user) {
userRepository.save(user);
}
}
Não há nada inerentemente complexo na definição da classeUserController.
Obviamente, o único detalhe de implementação que vale a pena observar aqui éthe use of the @CrossOrigin annotation. Como o nome indica, a anotação ativaCross-Origin Resource Sharing (CORS) no servidor.
This step isn’t always necessary. Como estamos implantando nosso front-end Angular emhttp://localhost:4200 e nosso back-end de inicialização emhttp://localhost:8080,the browser would otherwise deny requests from one to the other.
Com relação aos métodos do controlador,getUser() busca todas as entidadesUser do banco de dados. Da mesma forma, o métodoaddUser() persiste uma nova entidade no banco de dados, que é passada norequest body.
Para manter as coisas simples, deixamos deliberadamente de fora da implementação do controlador, disparandoSpring Boot validation antes de persistir uma entidade. Na produção, no entanto, não podemos confiar na entrada do usuário, então a validação do lado do servidor deve ser um recurso obrigatório.
2.5. Inicializando o aplicativo Spring Boot
Por fim, vamos criar uma classe de bootstrapping Spring Boot padrão e preencher o banco de dados com algumas entidadesUser:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CommandLineRunner init(UserRepository userRepository) {
return args -> {
Stream.of("John", "Julie", "Jennifer", "Helen", "Rachel").forEach(name -> {
User user = new User(name, name.toLowerCase() + "@domain.com");
userRepository.save(user);
});
userRepository.findAll().forEach(System.out::println);
};
}
}
Agora, vamos executar o aplicativo. Como esperado, devemos ver uma lista de entidadesUser impressa no console na inicialização:
User{id=1, name=John, [email protected]}
User{id=2, name=Julie, [email protected]}
User{id=3, name=Jennifer, [email protected]}
User{id=4, name=Helen, [email protected]}
User{id=5, name=Rachel, [email protected]}
3. A Aplicação Angular
Com nosso aplicativo de demonstração Spring Boot instalado e em execução, vamos agora criar um aplicativo Angular simples, capaz de consumir a API do controlador REST.
3.1. Instalação CLI Angular
UsaremosAngular CLI, um poderoso utilitário de linha de comando, para criar nosso aplicativo Angular.
Angular CLI é uma ferramenta extremamente valiosa desdeit allows us to create an entire Angular project from scratch, generating components, services, classes, and interfaces with just a few commands.
Depois de instalar onpm (Node Package Manager), abriremos um console de comando e digitaremos o comando:
npm install -g @angular/[email protected]
É isso aí. O comando acima instalará a versão mais recente do Angular CLI.
3.2. Andaime do projeto com CLI angular
De fato, podemos gerar nossa estrutura de aplicativos Angular a partir do zero. Mas, honestamente, essa é uma tarefa propensa a erros e demorada que devemos evitar em todos os casos.
Em vez disso, vamos deixar o Angular CLI fazer o trabalho árduo por nós. Então, vamos abrir um console de comando, navegar até a pasta onde queremos que nosso aplicativo seja criado e digitar o comando:
ng new angularclient
O comandonew irá gerar toda a estrutura do aplicativo dentro do diretórioangularclient.
3.3. O ponto de entrada do aplicativo Angular
Se olharmos dentro da pastaangularclient, veremos que o Angular CLI efetivamente criou um projeto inteiro para nós.
Angular’s application files use TypeScripthttps://www.typescriptlang.org/[,] a typed superset of JavaScript that compiles to plain JavaScript. No entanto, o ponto de entrada de qualquer aplicativo Angular é um arquivoindex.html simples e antigo.
Vamos editar este arquivo da seguinte maneira:
Spring Boot - Angular Application
Como podemos ver acima, incluímosBootstrap 4, para que possamos dar aos componentes de IU do nosso aplicativo uma aparência mais sofisticada. Claro, é possível escolher outro kit de interface do usuário entre os disponíveis.
Observe as tags<app-root></app-root> personalizadas dentro da seção<body>. À primeira vista, eles parecem um pouco estranhos, já que<app-root> não é um elemento HTML 5 padrão.
Vamos mantê-los ali, como<app-root> is the root selector that Angular uses for rendering the application’s root component.
3.4. O componente raizapp.component.ts
Para entender melhor como o Angular vincula um modelo HTML a um componente, vamos para o diretóriosrc/app e edite o arquivo TypeScriptapp.component.ts - o componente raiz:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title: string;
constructor() {
this.title = 'Spring Boot - Angular Application';
}
}
Por razões óbvias, não vamos nos aprofundar no aprendizado do TypeScript. Mesmo assim, vamos notar que o arquivo define uma classeAppComponent, que declara um campotitle do tipostring (minúsculas). Definitivamente, é JavaScript digitado.
Além disso, o construtor inicializa o campo com um valorstring, que é muito semelhante ao que fazemos em Java.
A parte mais relevante éthe @Component metadata marker or decorator, que define três elementos:
-
selector - o seletor HTML usado para vincular o componente ao arquivo de modelo HTML
-
templateUrl - o arquivo de modelo HTML associado ao componente
-
styleUrls - um ou mais arquivos CSS associados ao componente
Como esperado, podemos usar os arquivosapp.component.htmleapp.component.css para definir o modelo HTML e os estilos CSS do componente raiz.
Finalmente, o elementoselector liga todo o componente ao seletor<app-root> incluído no arquivoindex.html.
3.5. O arquivoapp.component.html
Como o arquivoapp.component.html nos permitedefine the root component’s HTML template - a classeAppComponent - vamos usá-lo para criar uma barra de navegação básica com dois botões.
Se clicarmos no primeiro botão, o Angular exibirá uma tabela contendo a lista deUser entidades armazenadas no banco de dados. Da mesma forma, se clicarmos no segundo, ele renderizará um formulário HTML, que podemos usar para adicionar novas entidades ao banco de dados:
{{ title }}
A maior parte do arquivo é HTML padrão, com algumas ressalvas.
The first one is the \{\{ title }} expression. The double curly braces \{\{ variable-name }} is the placeholder that Angular uses for performing variable interpolation.
Vamos ter em mente que a classeAppComponent inicializou o campotitle com o valorSpring Boot – Angular Application. Assim, Angular exibirá o valor desse campo no modelo. Da mesma forma, alterar o valor no construtor será refletido no modelo.
The second thing to note is the routerLink attribute.
Angular uses this attribute for routing requests through its routing module (mais sobre isso depois). Por agora, é suficiente saber que o módulo irá despachar uma solicitação para o caminho/users para um componente específico e uma solicitação para/adduser para outro componente.
Em cada caso, o modelo HTML associado ao componente correspondente será renderizado no espaço reservado<router-outlet></router-outlet>.
3.6. A classeUser
Como nosso aplicativo Angular buscará e persistirá entidadesUser no banco de dados, vamos implementar um modelo de domínio simples com TypeScript.
Vamos abrir um console de terminal e criar um diretóriomodel:
ng generate class user
O Angular CLI irá gerar uma classeUser vazia. Vamos preenchê-lo com alguns campos:
export class User {
id: string;
name: string;
email: string;
}
3.7. O serviçoUserService
Com a classeUser do domínio do lado do cliente já definida, vamos implementar uma classe de serviço que executa solicitações GET e POST para o endpointhttp://localhost:8080/users.
This will allow us to encapsulate access to the REST controller in a single class, which we can reuse throughout the entire application.
Vamos abrir um terminal de console, criar um diretórioservice e, dentro desse diretório, emitir o seguinte comando:
ng generate service user-service
Agora, vamos abrir o arquivouser.service.ts que o Angular CLI acabou de criar e refatorá-lo:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { User } from '../model/user';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class UserService {
private usersUrl: string;
constructor(private http: HttpClient) {
this.usersUrl = 'http://localhost:8080/users';
}
public findAll(): Observable {
return this.http.get(this.usersUrl);
}
public save(user: User) {
return this.http.post(this.usersUrl, user);
}
}
Não precisamos de um conhecimento sólido em TypeScript para entender como a classeUserService funciona. Simplificando, ele encapsula em um componente reutilizávelall the functionality required to consume the REST controller API that we implemented before no Spring Boot.
O métodofindAll() executa uma solicitação GET HTTP para o endpointhttp://localhost:8080/users viaAngular’s HttpClient. O método retorna uma instânciaObservable que contém uma matriz de objetosUser.
Da mesma forma, o métodosave() executa uma solicitação POST HTTP para o endpointhttp://localhost:8080/users.
Ao especificar o tipoUser nos métodos de solicitação deHttpClient, podemos consumir respostas de back-end de uma maneira mais fácil e eficaz.
Por último, vamosnotice the use of the @Injectable() metadata marker. This signals that the service should be created and injected via Angular’s dependency injectors.
3.8. O componenteUserListComponent
Nesse caso, a classeUserService é a fina camada intermediária entre o serviço REST e a camada de apresentação do aplicativo. Portanto, precisamos definir um componente responsável por renderizar a lista de entidadesUser persistidas no banco de dados.
Vamos abrir um console de terminal, criar um diretóriouser-list e gerar um componente de lista de usuários:
ng generate component user-list
O Angular CLI irá gerar uma classe de componente vazia que implementa a interfacengOnInit. A interface declara um método hookngOnInit(), que o Angular chama depois de terminar de instanciar a classe de implementação e também após chamar seu construtor.
Vamos refatorar a classe para que ela possa tomar uma instânciaUserService no construtor:
import { Component, OnInit } from '@angular/core';
import { User } from '../model/user';
import { UserService } from '../service/user.service';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
users: User[];
constructor(private userService: UserService) {
}
ngOnInit() {
this.userService.findAll().subscribe(data => {
this.users = data;
});
}
}
A implementação da classeUserListComponent é bastante autoexplicativa. Ele simplesmente usa o métodoUserService’s findAll() para buscar todas as entidades persistentes no banco de dados e as armazena no campousers.
Além disso, precisamos editar o arquivo HTML do componente,user-list.component.html, para criar a tabela que exibe a lista de entidades:
#
Name
Email
{{ user.id }}
{{ user.name }}
{{ user.email }}
Notice the use of the *ngFor directive. A diretiva é chamada derepeater, e podemos usá-la para iterar o conteúdo de uma variável e renderizar elementos HTML de forma iterativa. Neste caso, nós o usamos para renderizar dinamicamente as linhas da tabela.
Além disso, usamos a interpolação de variáveis para mostrarid,name eemail de cada usuário.
3.9. O componenteUserFormComponent
Da mesma forma, precisamos criar um componente que nos permite persistir um novo objetoUser no banco de dados.
Vamos criar um diretóriouser-form e digitar o seguinte:
ng generate component user-form
A seguir, vamos abrir o arquivouser-form.component.ts e adicionar à classeUserFormComponent um método para salvar um objetoUser:
import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from '../service/user.service';
import { User } from '../model/user';
@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html',
styleUrls: ['./user-form.component.css']
})
export class UserFormComponent {
user: User;
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService) {
this.user = new User();
}
onSubmit() {
this.userService.save(this.user).subscribe(result => this.gotoUserList());
}
gotoUserList() {
this.router.navigate(['/users']);
}
}
Nesse caso,UserFormComponent também leva uma instânciaUserService no construtor, que o métodoonSubmit() usa para salvar o objetoUser fornecido.
Como precisamos exibir novamente a lista atualizada de entidades depois de persistir uma nova, chamamos o métodogotoUserList() após a inserção, que redireciona o usuário para o caminho/users.
Além disso, precisamos editar o arquivouser-form.component.html e criar o formulário HTML para manter um novo usuário no banco de dados:
À primeira vista, o formulário parece bastante padrão. Mas ele encapsula muitas das funcionalidades do Angular nos bastidores.
Vamos notar o uso dethe ngSubmit directive, which calls the onSubmit() method when the form is submitted.
A seguir, definimos otemplate variable #userForm, so Angular adds automatically an NgForm directive, which allows us to keep track of the form as a whole.
A diretivaNgForm contém os controles que criamos para os elementos do formulário com uma diretivangModel e um atributoname e também monitora suas propriedades, incluindo seu estado.
The ngModel directive gives us two-way data binding functionality between the form controls and the client-side domain model – the User class.
Isso significa que os dados inseridos nos campos de entrada do formulário fluirão para o modelo - e vice-versa. As alterações nos dois elementos serão refletidas imediatamente através da manipulação do DOM.
Além disso,ngModel nos permite acompanhar o estado de cada controle de formulário e executarclient-side validation, adicionando a cada controle diferentes classes CSS e propriedades DOM.
No arquivo HTML acima, usamos as propriedades aplicadas aos controles do formulário apenas para exibir uma caixa de alerta quando os valores no formulário foram alterados.
3.10. O arquivoapp-routing.module.ts
Embora os componentes sejam funcionais isoladamente, ainda precisamos usar um mecanismo para chamá-los quando o usuário clicar nos botões na barra de navegação.
É aqui que oRouterModule entra em ação. Então, vamos abrir o arquivoapp-routing.module.ts e configurar o módulo, para que possa despachar solicitações para os componentes correspondentes:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UserListComponent } from './user-list/user-list.component';
import { UserFormComponent } from './user-form/user-form.component';
const routes: Routes = [
{ path: 'users', component: UserListComponent },
{ path: 'adduser', component: UserFormComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Como podemos ver acima,the Routes array instructs the router which component to display when a user clicks a link or specifies a URL into the browser address bar.
Uma rota é composta de duas partes:
-
Path – astring que corresponde ao URL na barra de endereço do navegador
-
Component - o componente a ser criado quando a rota estiver ativa (navegada)
Se o usuário clicar no botãoList Users, que vincula ao caminho/users, ou inserir a URL na barra de endereço do navegador, o roteador renderizará o arquivo de modelo do componenteUserListComponent no<router-outlet> placeholder.
Da mesma forma, se eles clicarem no botãoAdd User, ele renderizará o componenteUserFormComponent.
3.11. O arquivoapp.module.ts
Em seguida, precisamos editar o arquivoapp.module.ts, para que o Angular possa importar todos os módulos, componentes e serviços necessários.
Além disso, precisamos especificar qual provedor usaremos para criar e injetar a classeUserService. Caso contrário, o Angular não será capaz de injetá-lo nas classes de componentes:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserFormComponent } from './user-form/user-form.component';
import { UserService } from './service/user.service';
@NgModule({
declarations: [
AppComponent,
UserListComponent,
UserFormComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
4. Executando o aplicativo
Finalmente, estamos prontos para executar nosso aplicativo.
Para fazer isso, vamos primeiro executar o aplicativo Spring Boot, para que o serviço REST esteja ativo e escutando as solicitações.
Assim que o aplicativo Spring Boot for iniciado, vamos abrir um console de comando e digitar o seguinte comando:
ng serve --open
This will start Angular’s live development server and also open the browser at http://localhost:4200.
Devemos ver a barra de navegação com os botões para listar entidades existentes e adicionar novas. Se clicarmos no primeiro botão, veremos abaixo da barra de navegação uma tabela com a lista de entidades persistentes no banco de dados: Da mesma forma, clicar no segundo botão exibirá o formulário HTML para persistir uma nova entidade:
5. Conclusão
Neste tutorial,we learned how to build a basic web application with Spring Boot and Angular.
Como de costume, todos os exemplos de código mostrados neste tutorial estão disponíveisover on GitHub.