Introdução ao Spring Boot e Recursos
Introdução ao Spring Boot e Recursos
Teorico/Prático
Sumário
O que é Spring Boot?..................................................................................................................... 3
Objetivos básicosdo Spring Boot ........................................................................................... 3
Principais recursos do Spring Boot ........................................................................................ 3
Estrutura de projeto padrão para projetos Spring Boot ............................................................... 7
Não use o “default” package ................................................................................................. 7
Layout Típico ......................................................................................................................... 7
Estrutura do Projeto - Primeira abordagem.......................................................................... 8
Conteúdo Estático (static content)........................................................................................ 9
Conteúdo web dinâmico (templates).................................................................................. 10
Estrutura do Projeto - Segunda abordagem ....................................................................... 11
[Link]............................................................................................................................... 11
O que é o Spring Boot Starter Parent e como usá-lo? ........................................................ 12
Aplicativos web spring boot - usando JSP como visualizações ........................................... 13
Arquitetura do projeto Spring Boot ............................................................................................ 14
Arquitetura de três camadas (três camadas) ...................................................................... 15
Arquitetura Spring Boot Flow (exemplo) ............................................................................ 16
Java Spring Boot & Eclipse .......................................................................................................... 19
Pré-Requisitos ..................................................................................................................... 19
Instalando o Eclipse ............................................................................................................. 19
Spring Tools ......................................................................................................................... 25
Criando o projeto ................................................................................................................ 25
Criando projeto Spring Boot com Spring Initializer ..................................................................... 33
Web Service Spring Restful ......................................................................................................... 38
Estrutura do Diretório do Projeto ....................................................................................... 40
Springboot Annotations .............................................................................................................. 48
Projeto API de upload / download de arquivo de springboot .................................................... 53
Escrevendo APIs para upload e download de arquivos .......................................... 58
Spring Boot Thymeleaf CRUD ...................................................................................................... 77
Etapas de Desenvolvimento ................................................................................................ 78
Construindo um aplicativo de chat com Spring Boot e WebSocket ........................................... 90
Projeto Spring Boot integrado com Angular 9 .......................................................................... 116
Arquitetura do projeto ...................................................................................................... 117
Projeto Spring Boot com Angular eAPI Rest MongoDB ............................................................ 131
Criando back-end Spring Boot........................................................................................... 132
Apendice.................................................................................................................................... 152
Referencias: ............................................................................................................................... 153
3
Requisitos de sistema
Servlet Containers
• @ComponentScan
• @EntityScan
• @SpringBootApplication
Layout Típico
A equipe springboot geralmente recomenda que possamos localizar a
classe de aplicativo principal em um pacote raiz acima de outras
classes. A annotation @SpringBootApplication é freqüentemente colocada em
sua classe principal e define implicitamente um “pacote de pesquisa” básico para
certos itens. Por exemplo, se você estiver escrevendo um aplicativo JPA, o
pacote da classe anotada @SpringBootApplication é usado para
8
import [Link] ;
import
[Link] ;
@SpringBootApplication public class Application {
public static void main ( String [] args ) {
SpringApplication . run ( Application . class,
args);
}
}
• /static
• /public
• /resources
• / META-INF / resouces
[Link]-path-pattern = /resources/**
• FreeMarker - spring-boot-starter-freemarker
• Groovy - spring-boot-starter-groovy
• Thymeleaf - spring-boot-starter-thymeleaf
• Mustache - spring-boot-starter-mustache
[Link]
Em um projeto spring boot, a maioria dos módulos pode ser habilitada ou
desabilitada apenas adicionando um conjunto de iniciadores. Todos os projetos
Spring Boot normalmente usam spring-boot-starter-parent como o pai
no arqruivo [Link] .
12
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>[Link]</version>
</parent>
Por exemplo:
Conclusão
Pré-Requisitos
Java JDK 8 ou superior
Instalando o Eclipse
O site oficial do IDE Eclipse é [Link], e na página de downloads
podemos baixar a versão mais recente do IDE. Para preparar seu computador
para desenvolviemnto em Java, você precisará do JDK e de um ambiente de
desenvolvimento integrado, como o Eclipse (ou outro, como o NetBeans).
Vejamos os procedimentos necessários para instalar o Eclipse:
1 – Baixar e instalar o JDK (Java Development Kit) para que seja possível
desenvolver aplicações em Java. Se você quiser usar o Eclipse para desenvolver
em outras linguagens que não o Java, basta baixar e instalar o JRE (Java
Runtime Environment) para que seja possível rodar o Eclipse, pois ele é escrito
em Java também. A versão necessária é a 1.7.0 ou mais recente para rodar o
Eclipse.
[Link]
Na tela que se abrirá você deve escolher a primeira opção, “Eclipse IDE for
Java Developers“, pois nosso objetivo é usar o IDE para aprender programação
em Java.
21
Será peguntado onde seus projetos deverão ser armazenados. Para isso,
o Eclipse cria uma pasta especial denominada Workspace. Escolha um local no
seu disco, ou mantenha o padrão sugerido (dentro de seu diretório pessoal),
marque a caixa “Use this as the default and do not ask again” para tornar a pasta
padrão em futuros projetos e clique em OK:
Você verá a tela de boas-vindas do Eclipse IDE for Java, onde há links para
tutoriais, exemplos e outras informações sobre o software. Feche essa aba
clicando no “x” ao lado da palavra Welcome, no lado superior esquerdo:
24
Vale lembrar que também é altamente válido instalar o Spring Tool Suite (STS) ao
invés do eclipse puro. O STS é basicamente o eclipse configurado com o plugin spring
tools, otimizado para desenvolvimento com o spring framework
Configurando o eclipse
Spring Tools
Para ter uma melhor experiência com desenvolvimento Spring, recomendo
instalar o Spring Tools. Vá no menu Help > Eclipse Marketplace… e procure por
spring:
Instale a versão mais atual do Spring Tools (na data de escrita deste guia
é a 4.2.1) e reinicie o eclipse.
Criando o projeto
Com tudo configurado corretamente, hora de criarmos o projeto. Vá no
menu: File > New > Project… e selecione a opção Spring Starter Project que
está localizada abaixo do menu Spring Boot, conforme imagem abaixo:
26
Iniciando o projeto
Utilize o código abaixo para criar o endpoint que irá retornar "Hello World!":
package [Link];
import [Link];
import
[Link];
import
[Link];
import
[Link];
/**
* ExampleController
*
32
*
*/
@RestController
@RequestMapping("/api/example")
public class ExampleController {
@GetMapping("/hello-world")
public ResponseEntity<String> get() {
return [Link]("Hello World!");
}
}
Após clicar no botão de executar, você deverá ver a saída do log de inicialização
Vamos criar um projeto simples Spring boot – seu nome será hello world
- usando a ferramenta Spring Initializr e importar no Eclipse IDE.
• Project: Maven
• Language: Java
• Spring Boot: 2.3.3
• Java: 8
• Group: [Link]
• Artfact: demo
• Name: demo
• Description: Demo Project for Spring Boot
• Package name: [Link]
• package: jar (este é o valor padrão)
• Dependencies: Web, JPA, MySQL
Depois de clicar em Finish, o Maven levará algum tempo para baixar todas as
dependências e inicializar o projeto.
$ java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14,
mixed mode)
39
$ mvn -v
Apache Maven 3.3.9
(bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-
10T16:41:47+00:00)
Maven home: /usr/local/Cellar/maven/3.3.9/libexec
Java version: 1.8.0_102, vendor: Oracle Corporation
[Link]
{"id":1,"content":"Hello, World!"}
[Link]
{"id":1,"content":"Hello, User!"}
• Project: Maven
• Language: Java
• Spring Boot: 2.3.4
• Java: 8
• Group: [Link].
• Artfact: helloworld
• Name: helloworld
• Description: Demo Project for Spring Boot
• Package name: [Link]
• package: jar (este é o valor padrão)
• Dependencies: Web, JPA, MySQL
O arquivo [Link]
<groupId> [Link]</groupId>
<artifactId> helloworld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>helloworld<name>
<description>Demo project for Spring
Boot</description>
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-
parent</artifactId>
<version> [Link]</version>
<relativePath/> <!-- lookup parent from
repository -->
</parent>
42
<properties>
<[Link]>UTF-
8</[Link]>
<[Link]>UTF-
8</[Link]>
<[Link]>1.8</[Link]>
</properties>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-
web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-
test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-
plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring-boot-starter-parent
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-
parent</artifactId>
<version> [Link]</version>
</parent>
Parent Poms permitem que você gerencie as seguintes coisas para vários
projetos e módulos filhos:
{
" id " : 1 ,
" content " : " Hello, World! "
}
import [Link];
import [Link];
import [Link];
import [Link];
@RestController
public class GreetingController {
@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name",
defaultValue = "World") String name) {
return new Greeting([Link](),
[Link](template, name));
}
}
import [Link];
import
[Link];
@SpringBootApplication
public class HelloworldApplication {
[Link]([Link], args);
}
}
Executando o aplicativo
$ mvn spring-boot:run
Teste o serviço
Agora que o serviço está ativo, visite http: // localhost: 8080 / greeting ,
onde você verá:
48
Resumo
Springboot Annotations
1. @SpringBootApplication
import [Link] ;
import
[Link] ;
2. @EnableAutoConfiguration
import [Link] ;
import
[Link] ;
@EnableAutoConfiguration
public class Application {
public static void main ( String [] args ) {
SpringApplication . run ( Application . class, args);
}
}
3. @ConditionalOnClass e @ConditionalOnMissingClass
@Configuration
@ConditionalOnClass ( DataSource . Class)
class OracleAutoconfiguration {
// ...
}
4. @ConditionalOnBean e @ConditionalOnMissingBean
Os
annotations @ConditionalOnBean e @ConditionalOnMissingBean deixam de
incluir um bean com base na presença ou ausência de bean específicos.
@Bean
@ConditionalOnBean ( name = " dataSource " )
LocalContainerEntityManagerFactoryBean entityManagerFactory ()
{
// ...
}
@Configuration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService () { ... }
}
5. @ConditionalOnProperty
52
@Bean
@ConditionalOnProperty (
name = " usemysql " ,
havingValue = " local "
)
DataSource dataSource () {
// ...
}
6. @ConditionalOnResource
7. @ConditionalOnWebApplication e @ConditionalOnNotWebApplication
Os
annotations @ConditionalOnWebApplication e @ConditionalOnNotWebAppl
ication deixar de configuração ser incluídos dependendo se o aplicativo é uma
“aplicação web”. Um aplicativo da web é um aplicativo que usa um
Spring WebApplicationContext , define um escopo de sessão ou tem
um StandardServletEnvironment .
Código de exemplo de
annotation @ConditionalOnWebApplication : com essas annotations,
53
@ConditionalOnWebApplication
HealthCheckController healthCheckController () {
// ...
}
8. @ConditionalExpression
@Bean
@ConditionalOnExpression ( " $ {usemysql} && $ {mysqlserver ==
'local'} " )
DataSource dataSource () {
// ...
}
9. @Conditional
Criando o aplicativo
• Abra [Link]
• Insira o arquivo-demo no campo “Artefato”.
• Adicione Web na seção de dependências.
• Clique em Gerar para gerar e baixar o projeto.
## MULTIPART (MultipartProperties)
[Link]=true
[Link]-size-threshold=2KB
[Link]-file-size=200MB
[Link]-request-size=215MB
[Link]-dir=/Users/callicoder/uploads
package [Link];
import
[Link]
operties;
@ConfigurationProperties(prefix = "file")
return uploadDir;
[Link] = uploadDir;
}
57
Abra a classe
principal src/main/java/com/example/filedemo/FileDemoApplicat
package [Link];
import [Link];
import [Link];
import
[Link];
import
[Link]
ies;
@SpringBootApplication
@EnableConfigurationProperties({
[Link]
})
[Link]([Link], args);
[Link]
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
import
[Link];
import [Link];
import [Link];
59
import [Link];
import [Link];
import [Link];
@RestController
@Autowired
@PostMapping("/uploadFile")
String fileDownloadUri =
[Link]()
.path("/downloadFile/")
.path(fileName)
.toUriString();
[Link](), [Link]());
@PostMapping("/uploadMultipleFiles")
public List<UploadFileResponse>
uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
return [Link](files)
.stream()
60
.collect([Link]());
@GetMapping("/downloadFile/{fileName:.+}")
Resource resource =
[Link](fileName);
try {
contentType =
[Link]().getMimeType([Link]().getAbsolute
Path());
if(contentType == null) {
contentType = "application/octet-stream";
return [Link]()
.contentType([Link](contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + [Link]() + "\"")
.body(resource);
}
61
UploadFileResponse
package [Link];
[Link] = fileName;
[Link] = fileDownloadUri;
[Link] = fileType;
[Link] = size;
}
62
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Service
@Autowired
public FileStorageService(FileStorageProperties
fileStorageProperties) {
[Link] =
[Link]([Link]())
.toAbsolutePath().normalize();
try {
[Link]([Link]);
String fileName =
[Link]([Link]());
try {
if([Link]("..")) {
Path targetLocation =
[Link](fileName);
[Link]([Link](), targetLocation,
StandardCopyOption.REPLACE_EXISTING);
64
return fileName;
try {
Path filePath =
[Link](fileName).normalize();
if([Link]()) {
return resource;
} else {
1. FileStorageException
65
package [Link];
super(message);
super(message, cause);
2. MyFileNotFoundException
package [Link];
import [Link];
import [Link];
@ResponseStatus(HttpStatus.NOT_FOUND)
super(message);
}
66
super(message, cause);
o Spring boot responda com um status 404 Not Found quando essa exceção
for lançada.
Desenvolvendo o Front-End
static
└── css
└── [Link]
└── js
└── [Link]
└── [Link]
O template HTML
<!DOCTYPE html>
<html>
<head>
67
</head>
<body>
<noscript>
</noscript>
<div class="upload-container">
<div class="upload-header">
</div>
<div class="upload-content">
<div class="single-upload">
<form id="singleUploadForm"
name="singleUploadForm">
<input id="singleFileUploadInput"
type="file" name="file" class="file-input" required />
</form>
<div class="upload-response">
<div id="singleFileUploadError"></div>
<div id="singleFileUploadSuccess"></div>
</div>
</div>
<div class="multiple-upload">
<form id="multipleUploadForm"
name="multipleUploadForm">
68
<input id="multipleFileUploadInput"
type="file" name="files" class="file-input" multiple required />
</form>
<div class="upload-response">
<div id="multipleFileUploadError"></div>
<div
id="multipleFileUploadSuccess"></div>
</div>
</div>
</div>
</div>
</body>
</html>
O arquivo Javascript
'use strict';
var singleUploadForm =
[Link]('#singleUploadForm');
var singleFileUploadInput =
[Link]('#singleFileUploadInput');
var singleFileUploadError =
[Link]('#singleFileUploadError');
var singleFileUploadSuccess =
[Link]('#singleFileUploadSuccess');
var multipleUploadForm =
[Link]('#multipleUploadForm');
var multipleFileUploadInput =
[Link]('#multipleFileUploadInput');
var multipleFileUploadError =
[Link]('#multipleFileUploadError');
69
var multipleFileUploadSuccess =
[Link]('#multipleFileUploadSuccess');
function uploadSingleFile(file) {
[Link]("file", file);
[Link]("POST", "/uploadFile");
[Link] = function() {
[Link]([Link]);
if([Link] == 200) {
[Link] = "none";
[Link] = "<p>File
Uploaded Successfully.</p><p>DownloadUrl : <a href='" +
[Link] + "' target='_blank'>" +
[Link] + "</a></p>";
[Link] = "block";
} else {
[Link] = "none";
[Link](formData);
function uploadMultipleFiles(files) {
[Link]("files", files[index]);
70
[Link]("POST", "/uploadMultipleFiles");
[Link] = function() {
[Link]([Link]);
if([Link] == 200) {
[Link] = "none";
[Link] = content;
[Link] = "block";
} else {
[Link] = "none";
[Link](formData);
[Link]('submit', function(event){
if([Link] === 0) {
71
[Link] = "block";
uploadSingleFile(files[0]);
[Link]();
}, true);
[Link]('submit', function(event){
if([Link] === 0) {
[Link] = "block";
uploadMultipleFiles(files);
[Link]();
}, true);
O arquivo CSS
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
body {
margin: 0;
72
padding: 0;
font-weight: 400;
font-size: 1rem;
line-height: 1.58;
color: #333;
background-color: #f4f4f4;
body:before {
height: 50%;
width: 100%;
position: absolute;
top: 0;
left: 0;
background: #128ff2;
content: "";
z-index: 0;
.clearfix:after {
display: block;
content: "";
clear: both;
margin-top: 20px;
margin-bottom: 20px;
}
73
h1 {
font-size: 1.7em;
a {
color: #128ff2;
button {
box-shadow: none;
font-size: 14px;
outline: none;
line-height: 100%;
white-space: nowrap;
vertical-align: middle;
border-radius: 2px;
cursor: pointer;
min-height: 38px;
[Link] {
background-color: #128ff2;
color: #fff;
input {
74
font-size: 1rem;
input[type="file"] {
padding: 6px;
max-width: 100%;
.file-input {
width: 100%;
.submit-btn {
display: block;
margin-top: 15px;
min-width: 100px;
.file-input {
.submit-btn {
display: inline-block;
margin-top: 0;
margin-left: 10px;
}
75
.upload-container {
max-width: 700px;
margin-left: auto;
margin-right: auto;
background-color: #fff;
margin-top: 60px;
min-height: 400px;
position: relative;
padding: 20px;
.upload-header {
.upload-header h2 {
font-weight: 500;
.single-upload {
padding-bottom: 20px;
margin-bottom: 20px;
.upload-response {
overflow-x: hidden;
word-break: break-all;
}
76
Opção JQuery
$('#singleUploadForm').submit(function(event) {
// You can directly create form data from the form element
// (Or you could get the files from input element and append
them to FormData as we did in vanilla javascript)
$.ajax({
type: "POST",
enctype: 'multipart/form-data',
url: "/uploadFile",
data: formData,
processData: false,
contentType: false,
[Link](response);
// process response
},
[Link](error);
// process error
});
[Link]();
});
77
Conclusão
Premissa
Etapas de Desenvolvimento
1. Criando um aplicativo Spring Boot
2. Estrutura do Projeto
3. Dependências Maven - [Link]
4. Camada de Domínio
5. A Camada de Repositório
6. A camada de controlador
7. A Camada de Visualização
8. Executando o aplicativo
9. Demo
2. Estrutura do Projeto
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-
thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-
plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Observe que estamos indo com os valores padrão, não adicionamos nada
no arquivo de configuração [Link] .
4. Camada de Domínio
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Entity
public class Student {
@Id
@GeneratedValue(strategy = [Link])
private long id;
@Column(name = "phone_no")
private long phoneNo;
public Student() {}
return phoneNo;
}
5. A Camada do Repositório
import [Link];
import [Link];
import [Link];
import [Link];
@Repository
public interface StudentRepository extends CrudRepository
<Student, Long> {
List<Student> findByName(String name);
}
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import
[Link];
@Controller
@RequestMapping("/students/")
public class StudentController {
@Autowired
public StudentController(StudentRepository studentRepository)
{
[Link] = studentRepository;
}
@GetMapping("signup")
public String showSignUpForm(Student student) {
return "add-student";
}
@GetMapping("list")
public String showUpdateForm(Model model) {
[Link]("students",
[Link]());
return "index";
}
@PostMapping("add")
public String addStudent(@Valid Student student, BindingResult
result, Model model) {
if ([Link]()) {
return "add-student";
}
[Link](student);
return "redirect:list";
}
84
@GetMapping("edit/{id}")
public String showUpdateForm(@PathVariable("id") long id,
Model model) {
Student student = [Link](id)
.orElseThrow(() - > new
IllegalArgumentException("Invalid student Id:" + id));
[Link]("student", student);
return "update-student";
}
@PostMapping("update/{id}")
public String updateStudent(@PathVariable("id") long id,
@Valid Student student, BindingResult result,
Model model) {
if ([Link]()) {
[Link](id);
return "update-student";
}
[Link](student);
[Link]("students",
[Link]());
return "index";
}
@GetMapping("delete/{id}")
public String deleteStudent(@PathVariable("id") long id, Model
model) {
Student student = [Link](id)
.orElseThrow(() - > new
IllegalArgumentException("Invalid student Id:" + id));
[Link](student);
[Link]("students",
[Link]());
return "index";
}
}
7. A Camada de Visualização
[Link]
<!DOCTYPE html>
<html xmlns:th="[Link]
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Add User</title>
<meta name="viewport" content="width=device-width, initial-
scale=1">
<link rel="stylesheet"
href="[Link]
.[Link]" integrity="sha384-
MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
crossorigin="anonymous">
<link rel="stylesheet"
href="[Link]
integrity="sha384-
5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz"
crossorigin="anonymous">
<!-- <link rel="stylesheet" href="../css/[Link]"> --
>
</head>
<body>
<div class="container my-5">
<h3> Add Student</h3>
<div class="card">
<div class="card-body">
<div class="col-md-10">
<form action="#"
th:action="@{/students/add}" th:object="${student}" method="post">
<div class="row">
<div class="form-group col-md-8">
<label for="name" class="col-
form-label">Name</label> <input type="text" th:field="*{name}"
class="form-control" id="name" placeholder="Name"> <span
th:if="${#[Link]('name')}" th:errors="*{name}" class="text-
danger"></span>
</div>
<div class="form-group col-md-8">
<label for="email" class="col-
form-label">Email</label> <input type="text" th:field="*{email}"
class="form-control" id="email" placeholder="Email"> <span
th:if="${#[Link]('email')}" th:errors="*{email}"
class="text-danger"></span>
</div>
<div class="col-md-6">
86
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="[Link]
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Users</title>
<meta name="viewport" content="width=device-width, initial-
scale=1">
<link rel="stylesheet"
href="[Link]
.[Link]" integrity="sha384-
MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
crossorigin="anonymous">
<link rel="stylesheet"
href="[Link]
integrity="sha384-
5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz"
crossorigin="anonymous">
<!-- <link rel="stylesheet" href="../css/[Link]"> --
>
</head>
<body>
<div class="container my-2">
<div class="card">
<div class="card-body">
<div th:switch="${students}" class="container
my-5">
<p class="my-5">
<a href="/students/signup" class="btn
btn-primary"><i
class="fas fa-user-plus ml-2"> Add Student</i></a>
</p>
<div class="col-md-10">
<h2 th:case="null">No Students yet!</h2>
<div th:case="*">
87
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="[Link]
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Update User</title>
<meta name="viewport" content="width=device-width, initial-
scale=1">
<link rel="stylesheet"
href="[Link]
.[Link]" integrity="sha384-
MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
crossorigin="anonymous">
88
<link rel="stylesheet"
href="[Link]
integrity="sha384-
5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz"
crossorigin="anonymous">
</head>
<body>
<div class="container my-5">
<h3> Update Student</h3>
<div class="card">
<div class="card-body">
<div class="col-md-8">
<form action="#"
th:action="@{/students/update/{id}(id=${[Link]})}"
th:object="${student}" method="post">
<div class="row">
<div class="form-group col-md-6">
<label for="name" class="col-
form-label">Name</label> <input type="text" th:field="*{name}"
class="form-control" id="name" placeholder="Name"> <span
th:if="${#[Link]('name')}" th:errors="*{name}" class="text-
danger"></span>
</div>
<div class="form-group col-md-8">
<label for="email" class="col-
form-label">Email</label> <input type="text" th:field="*{email}"
class="form-control" id="email" placeholder="Email"> <span
th:if="${#[Link]('email')}" th:errors="*{email}"
class="text-danger"></span>
</div>
<div class="form-group col-md-8">
<label for="phoneNo" class="col-
form-label">Phone No</label> <input type="text" th:field="*{phoneNo}"
class="form-control" id="phoneNo" placeholder="phoneNo"> <span
th:if="${#[Link]('phoneNo')}" th:errors="phoneNo"
class="text-danger"></span>
</div>
</html>
89
8. Executando o aplicativo
import [Link];
import
[Link];
@SpringBootApplication
public class Application {
9. Executando a aplicação
Você pode abrir o aplicativo em duas abas, fazer o login com nomes de
usuário diferentes e começar a enviar mensagens.
Criando o aplicativo
• Vá para [Link] .
• Insira o valor do Artifact como websocket-demo .
• Adicione Websocket na seção de dependências.
• Clique em Gerar projeto para baixar o projeto.
• Extraia o arquivo zip baixado.
Configuração WebSocket
package [Link];
import [Link];
import
[Link];
import [Link].*;
@Configuration
@EnableWebSocketMessageBroker
@Override
[Link]("/ws").withSockJS();
@Override
[Link]("/app");
[Link]("/topic");
Criação do ChatMessagemodel
package [Link];
95
CHAT,
JOIN,
LEAVE
return type;
[Link] = type;
return content;
[Link] = content;
return sender;
}
96
[Link] = sender;
package [Link];
import [Link];
import
[Link];
import [Link];
import [Link];
import
[Link];
import [Link];
@Controller
@MessageMapping("/[Link]")
@SendTo("/topic/public")
return chatMessage;
@MessageMapping("/[Link]")
@SendTo("/topic/public")
SimpMessageHeaderAccessor
headerAccessor) {
[Link]().put("username",
[Link]());
return chatMessage;
package [Link];
import [Link];
import [Link];
import [Link];
98
import [Link];
import [Link];
import
[Link];
import
[Link];
import [Link];
import
[Link];
import
[Link];
@Component
@Autowired
@EventListener
public void
handleWebSocketConnectListener(SessionConnectedEvent event) {
@EventListener
public void
handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor =
[Link]([Link]());
if(username != null) {
[Link]([Link]);
[Link](username);
[Link]("/topic/public",
chatMessage);
Criação do front-end
static
└── css
└── [Link]
└── js
└── [Link]
└── [Link]
100
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<noscript>
</noscript>
<div id="username-page">
<div class="username-page-container">
<div class="form-group">
</div>
<div class="form-group">
</div>
</form>
</div>
</div>
<div class="chat-container">
<div class="chat-header">
</div>
<div class="connecting">
Connecting...
</div>
<ul id="messageArea">
</ul>
<div class="form-group">
<button type="submit"
class="primary">Send</button>
</div>
</div>
</form>
</div>
</div>
102
<script src="[Link]
client/1.1.4/[Link]"></script>
<script
src="[Link]
s"></script>
<script src="/js/[Link]"></script>
</body>
</html>
2. JavaScript - [Link]
'use strict';
var colors = [
];
function connect(event) {
username = [Link]('#name').[Link]();
if(username) {
[Link]('hidden');
[Link]('hidden');
stompClient = [Link](socket);
[Link]();
function onConnected() {
[Link]('/topic/public', onMessageReceived);
[Link]("/app/[Link]",
{},
[Link]('hidden');
}
104
function onError(error) {
[Link] = 'red';
function sendMessage(event) {
var chatMessage = {
sender: username,
content: [Link],
type: 'CHAT'
};
[Link]("/app/[Link]", {},
[Link](chatMessage));
[Link] = '';
[Link]();
function onMessageReceived(payload) {
[Link]('event-message');
[Link]('event-message');
} else {
[Link]('chat-message');
var avatarText =
[Link]([Link][0]);
[Link](avatarText);
[Link]['background-color'] =
getAvatarColor([Link]);
[Link](avatarElement);
var usernameText =
[Link]([Link]);
[Link](usernameText);
[Link](usernameElement);
[Link](messageText);
[Link](textElement);
[Link](messageElement);
[Link] = [Link];
}
106
function getAvatarColor(messageSender) {
var hash = 0;
return colors[index];
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
107
html,body {
height: 100%;
overflow: hidden;
body {
margin: 0;
padding: 0;
font-weight: 400;
font-size: 1rem;
line-height: 1.58;
color: #333;
background-color: #f4f4f4;
height: 100%;
body:before {
height: 50%;
width: 100%;
position: absolute;
top: 0;
left: 0;
background: #128ff2;
content: "";
z-index: 0;
.clearfix:after {
display: block;
108
content: "";
clear: both;
.hidden {
display: none;
.form-control {
width: 100%;
min-height: 38px;
font-size: 15px;
.form-group {
margin-bottom: 15px;
input {
padding-left: 10px;
outline: none;
margin-top: 20px;
margin-bottom: 20px;
h1 {
font-size: 1.7em;
109
a {
color: #128ff2;
button {
box-shadow: none;
font-size: 14px;
outline: none;
line-height: 100%;
white-space: nowrap;
vertical-align: middle;
border-radius: 2px;
cursor: pointer;
min-height: 38px;
[Link] {
background-color: #e8e8e8;
color: #333;
[Link] {
background-color: #128ff2;
color: #fff;
110
[Link] {
background-color: #ff4743;
color: #fff;
#username-page {
text-align: center;
.username-page-container {
background: #fff;
border-radius: 2px;
width: 100%;
max-width: 500px;
display: inline-block;
margin-top: 42px;
vertical-align: middle;
position: relative;
min-height: 250px;
position: absolute;
top: 50%;
left: 0;
right: 0;
margin: 0 auto;
margin-top: -160px;
}
111
.username-page-container .username-submit {
margin-top: 10px;
#chat-page {
position: relative;
height: 100%;
.chat-container {
max-width: 700px;
margin-left: auto;
margin-right: auto;
background-color: #fff;
margin-top: 30px;
max-height: 600px;
position: relative;
#chat-page ul {
list-style-type: none;
background-color: #FFF;
margin: 0;
overflow: auto;
overflow-y: scroll;
#chat-page #messageForm {
padding: 20px;
#chat-page ul li {
line-height: 1.5rem;
margin: 0;
#chat-page ul li p {
margin: 0;
#chat-page .event-message {
width: 100%;
text-align: center;
clear: both;
#chat-page .event-message p {
color: #777;
font-size: 14px;
word-wrap: break-word;
#chat-page .chat-message {
padding-left: 68px;
113
position: relative;
#chat-page .chat-message i {
position: absolute;
width: 42px;
height: 42px;
overflow: hidden;
left: 10px;
display: inline-block;
vertical-align: middle;
font-size: 18px;
line-height: 42px;
color: #fff;
text-align: center;
border-radius: 50%;
font-style: normal;
text-transform: uppercase;
color: #333;
font-weight: 600;
#chat-page .chat-message p {
color: #43464b;
float: left;
114
float: left;
width: 80px;
height: 38px;
margin-left: 5px;
.chat-header {
text-align: center;
padding: 15px;
.chat-header h2 {
margin: 0;
font-weight: 500;
.connecting {
padding-top: 5px;
text-align: center;
color: #777;
position: absolute;
top: 65px;
width: 100%;
}
115
.chat-container {
margin-left: 10px;
margin-right: 10px;
margin-top: 10px;
.chat-container {
.username-page-container {
width: auto;
margin-left: 15px;
margin-right: 15px;
padding: 25px;
#chat-page ul {
width: 65px;
.chat-header {
padding: 10px;
.connecting {
top: 60px;
.chat-header h2 {
font-size: 1.1em;
Executando o aplicativo
$ mvn spring-boot:run
Arquitetura do projeto
Implementação de recursos
• Criar um employee
• Atualizar um employee
• Lista de employees
• Excluir employee
118
• Ver Employee
Excluir employee
• Project : Maven
• Versão Java : 8
• Spring Boot : 2.3.4
• Grupo : [Link]
• Artfact : angularspringboot
• Name : angularspringboot
• Description: Sringboot Angular Project
• PackageName : [Link]
• pacote : jar (este é o valor padrão)
121
3. O arquivo [Link]
<modelVersion>4.0.0</modelVersion>
<groupId>[Link]</groupId>
<artifactId>angularspringboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>angularspringboot</name>
<description>Sprinboot Angular Project</description>
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>[Link]</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<[Link]>UTF-
8</[Link]>
<[Link]>UTF-
8</[Link]>
<[Link]>1.8</[Link]>
</properties>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-data-
jpa</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-
plugin</artifactId>
</plugin>
</plugins>
123
</build>
</project>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
[Link]-path=/springboot-crud-rest
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
124
import [Link];
@Entity
@Table(name = "employees")
public class Employee {
public Employee() {
@Id
@GeneratedValue(strategy = [Link])
public long getId() {
return id;
}
public void setId(long id) {
[Link] = id;
}
@Override
public String toString() {
return "Employee [id=" + id + ", firstName=" + firstName
+ ", lastName=" + lastName + ", emailId=" + emailId
+ "]";
}
125
import [Link];
import [Link];
import
[Link];
@Repository
public interface EmployeeRepository extends
JpaRepository<Employee, Long>{
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import
[Link]
FoundException;
import
[Link];
import
[Link]
pository;
@GetMapping("/employees")
126
@GetMapping("/employees/{id}")
public ResponseEntity<Employee>
getEmployeeById(@PathVariable(value = "id") Long employeeId)
throws ResourceNotFoundException {
Employee employee =
[Link](employeeId)
.orElseThrow(() -> new
ResourceNotFoundException("Employee not found for this id :: " +
employeeId));
return [Link]().body(employee);
}
@PostMapping("/employees")
public Employee createEmployee(@Valid @RequestBody Employee
employee) {
return [Link](employee);
}
@PutMapping("/employees/{id}")
public ResponseEntity<Employee>
updateEmployee(@PathVariable(value = "id") Long employeeId,
@Valid @RequestBody Employee employeeDetails) throws
ResourceNotFoundException {
Employee employee =
[Link](employeeId)
.orElseThrow(() -> new
ResourceNotFoundException("Employee not found for this id :: " +
employeeId));
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
final Employee updatedEmployee =
[Link](employee);
return [Link](updatedEmployee);
}
@DeleteMapping("/employees/{id}")
public Map<String, Boolean>
deleteEmployee(@PathVariable(value = "id") Long employeeId)
throws ResourceNotFoundException {
Employee employee =
[Link](employeeId)
.orElseThrow(() -> new
ResourceNotFoundException("Employee not found for this id :: " +
employeeId));
[Link](employee);
Map<String, Boolean> response = new HashMap<>();
[Link]("deleted", [Link]);
return response;
}
}
127
@CrossOrigin(origins = "[Link]
@RestController
@RequestMapping("/api/v1")
public class EmployeeController {
// ....
}
Aqui está o que acontece quando você dispara uma solicitação para um
recurso não encontrado: http: // localhost: 8080 / some-dummy-url
{
"timestamp": 1512713804164,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/some-dummy-url"
}
Vamos ver o que Spring Boot faz quando uma exceção é lançada de
um Resource . podemos especificar o Status de Resposta para uma exceção
específica junto com a definição da Exceção
com a annotation '@ResponseStatus' .
128
import [Link];
import [Link];
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception{
import [Link];
return details;
}
}
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler([Link])
public ResponseEntity<?>
resourceNotFoundException(ResourceNotFoundException ex, WebRequest
request) {
ErrorDetails errorDetails = new ErrorDetails(new
Date(), [Link](), [Link](false));
return new ResponseEntity<>(errorDetails,
HttpStatus.NOT_FOUND);
}
@ExceptionHandler([Link])
public ResponseEntity<?> globleExcpetionHandler(Exception
ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(),
[Link](), [Link](false));
return new ResponseEntity<>(errorDetails,
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
9. Aplicação em execução
import [Link];
import
[Link];
@SpringBootApplication
public class Application {
Use os endpoints abaixo Rest para testar CRUD Rest APIs e no aplicativo
Angular.
Criar employee:
A seguir está uma captura de tela do todo-app que iremos construir neste
projeto.
• Vá para [Link] .
• Insira o valor do artefato como todoapp .
• Adicione Spring Web e Spring Data MongoDB na seção de
dependências.
• Clique em Gerar para gerar e baixar o projeto.
133
# MONGODB (MongoProperties)
[Link]=mongodb://localhost:27017/todo
app
Observe que, para que a configuração acima funcione, você precisa ter o
MongoDB instalado em seu sistema.
mongodb://<dbuser>:<dbpassword>@[Link]
9/testdatabase
3. Criação do TodoModel
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Document(collection="todos")
@Id
@NotBlank
@Size(max=100)
@Indexed(unique=true)
public Todo() {
super();
[Link] = title;
return id;
[Link] = id;
return title;
[Link] = title;
return completed;
[Link] = completed;
return createdAt;
[Link] = createdAt;
@Override
return [Link](
package [Link];
import [Link];
import
[Link];
import [Link];
@Repository
Por fim, vamos criar as APIs que serão expostas ao client. Crie um novo
pacote controllers dentro [Link] e adicione um
arquivo [Link] dentro do pacote controllers com o seguinte
código:
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
@RestController
@RequestMapping("/api")
@CrossOrigin("*")
public class TodoController {
@Autowired
TodoRepository todoRepository;
@GetMapping("/todos")
public List<Todo> getAllTodos() {
Sort sortByCreatedAtDesc = new Sort([Link],
"createdAt");
return [Link](sortByCreatedAtDesc);
}
@PostMapping("/todos")
public Todo createTodo(@Valid @RequestBody Todo todo) {
[Link](false);
return [Link](todo);
}
@GetMapping(value="/todos/{id}")
public ResponseEntity<Todo> getTodoById(@PathVariable("id")
String id) {
return [Link](id)
.map(todo -> [Link]().body(todo))
.orElse([Link]().build());
}
@PutMapping(value="/todos/{id}")
public ResponseEntity<Todo> updateTodo(@PathVariable("id")
String id,
139
@Valid @RequestBody
Todo todo) {
return [Link](id)
.map(todoData -> {
[Link]([Link]());
[Link]([Link]());
Todo updatedTodo =
[Link](todoData);
return
[Link]().body(updatedTodo);
}).orElse([Link]().build());
}
@DeleteMapping(value="/todos/{id}")
public ResponseEntity<?> deleteTodo(@PathVariable("id")
String id) {
return [Link](id)
.map(todo -> {
[Link](id);
return [Link]().build();
}).orElse([Link]().build());
}
}
mvn spring-boot:run
O aplicativo será iniciado na porta 8080. Você pode testar o backend apis
usando o carteiro ou qualquer outro cliente restante de sua escolha.
$ ng new angular-frontend
2. Executando o aplicativo
$ cd angular-frontend
$ ng serve --open
<main>
<todo-list></todo-list>
</main>
id: string;
title: string;
completed: boolean;
createdAt: Date;
@Component({
selector: 'todo-list',
templateUrl: './[Link]'
142
})
ngOnInit(): void {
[Link]();
}
getTodos(): void {
clearEditing(): void {
}
}
@NgModule({
declarations: [
AppComponent,
TodoListComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Observe que,
adicionamos FormsModule e HttpModule dentro [Link] porque vamos
precisar deles para lidar com a vinculação de formulários e chamar as APIs
restantes, respectivamente.
<div class="todo-content">
<h1 class="page-title">My Todos</h1>
<div class="todo-create">
<form #todoForm="ngForm" (ngSubmit)="createTodo(todoForm)"
novalidate>
<input type="text" id="title" class="form-control"
placeholder="Type a todo and press enter..."
required
name="title" [(ngModel)]="[Link]"
#title="ngModel" >
<div *ngIf="[Link] && [Link]"
class="alert alert-danger">
144
<div [hidden]="![Link]">
Title is required.
</div>
</div>
</form>
</div>
<ul class="todo-list">
<li *ngFor="let todo of todos"
[[Link]]="[Link] === true" >
<div class="todo-row" *ngIf="!editing || [Link]
!= [Link]">
<a class="todo-completed"
(click)="toggleCompleted(todo)">
<i class="material-icons toggle-completed-
checkbox"></i>
</a>
<span class="todo-title">
{{[Link]}}
</span>
<span class="todo-actions">
<a (click)="editTodo(todo)">
<i class="material-icons edit">edit</i>
</a>
<a (click)="deleteTodo([Link])">
<i class="material-icons delete">clear</i>
</a>
</span>
</div>
<div class="todo-edit" *ngIf="editing && [Link]
=== [Link]">
<input class="form-control" type="text"
[(ngModel)]="[Link]" required>
<span class="edit-actions">
<a (click)="updateTodo(editingTodo)">
<i class="material-icons">done</i>
</a>
<a (click)="clearEditing()">
<i class="material-icons">clear</i>
</a>
</span>
</div>
</li>
</ul>
<div class="no-todos" *ngIf="todos && [Link] == 0">
<p>No Todos Found!</p>
</div>
</div>
/* You can add global styles to this file, and also import other
style files */
body {
font-size: 18px;
line-height: 1.58;
background: #d53369;
background: -webkit-linear-gradient(to left, #cbad6d,
#d53369);
background: linear-gradient(to left, #cbad6d, #d53369);
color: #333;
}
h1 {
font-size: 36px;
}
* {
box-sizing: border-box;
}
i {
vertical-align: middle;
color: #626262;
}
input {
border: 1px solid #E8E8E8;
}
.page-title {
text-align: center;
}
.todo-content {
max-width: 650px;
width: 100%;
margin: 0 auto;
margin-top: 60px;
background-color: #fff;
padding: 15px;
box-shadow: 0 0 4px rgba(0,0,0,.14), 0 4px 8px
rgba(0,0,0,.28);
}
.form-control {
font-size: 16px;
padding-left: 15px;
146
outline: none;
border: 1px solid #E8E8E8;
}
.form-control:focus {
border: 1px solid #626262;
}
.todo-content .form-control {
width: 100%;
height: 50px;
}
.todo-content .todo-create {
padding-bottom: 30px;
border-bottom: 1px solid #e8e8e8;
}
.todo-content .alert-danger {
padding-left: 15px;
font-size: 14px;
color: red;
padding-top: 5px;
}
.todo-content ul {
list-style: none;
margin: 0;
padding: 0;
max-height: 450px;
padding-left: 15px;
padding-right: 15px;
margin-left: -15px;
margin-right: -15px;
overflow-y: scroll;
}
.todo-content ul li {
padding-top: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #E8E8E8;
}
.todo-content ul li span {
display: inline-block;
vertical-align: middle;
}
.todo-content .todo-title {
width: calc(100% - 160px);
margin-left: 10px;
overflow: hidden;
text-overflow: ellipsis;
}
.todo-content .todo-completed {
display: inline-block;
text-align: center;
width:35px;
height:35px;
cursor: pointer;
147
.todo-content .todo-completed i {
font-size: 20px;
}
.no-todos {
text-align: center;
}
.toggle-completed-checkbox:before {
content: 'check_box_outline_blank';
}
[Link] .toggle-completed-checkbox:before {
content: 'check_box';
}
[Link] .todo-title {
text-decoration: line-through;
color: #757575;
}
[Link] i {
color: #757575;
}
<link rel="stylesheet"
href="[Link]
@Injectable()
export class TodoService {
private baseUrl = '[Link]
getTodos(): Promise<Todo[]> {
return [Link]([Link] + '/api/todos/')
.toPromise()
.then(response => [Link]() as Todo[])
.catch([Link]);
}
// Inside [Link]
// Inside [Link]
providers: [TodoService]
@Component({
selector: 'todo-list',
templateUrl: './[Link]'
})
constructor(
private todoService: TodoService,
) {}
ngOnInit(): void {
[Link]();
}
getTodos(): void {
[Link]()
.then(todos => [Link] = todos );
}
clearEditing(): void {
[Link] = new Todo();
151
[Link] = false;
}
}
$ cd todoapp
$ mvn spring-boot:run
$ cd angular-frontend
$ ng serve --open
152
Apendice
[Link]-dir=/Users/seuusuario/uploads
153
Referencias:
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]