Identificação
- Duração: 2 horas
- Tipo: teoria aplicada e laboratório
- Entrega: integração do painel com API simulada
- Laboratório:
../exemplos/aula-4.7/index.html
Introdução
O que é HTTP?
HTTP é o protocolo usado para trocar mensagens entre clientes e servidores na Web. O navegador ou aplicação envia uma requisição e o servidor devolve uma resposta.
Angular no navegador ── requisição HTTP ──▶ API NestJS
Angular no navegador ◀─ resposta HTTP ───── API NestJS
HTTP define formato e significado da comunicação. Ele não é o banco de dados, a linguagem TypeScript nem a API em si.
O que é cliente?
Cliente é quem inicia a requisição. Nesta etapa, a aplicação Angular é o cliente. Ela solicita uma lista de tarefas ou envia um novo cadastro.
O que é servidor?
Servidor é o sistema que recebe a requisição, aplica regras e produz uma resposta. No curso, esse papel será da API NestJS. Ela poderá utilizar Prisma para acessar MySQL.
Angular → HTTP → NestJS → Prisma → MySQL
O Angular nunca deve acessar o MySQL diretamente. Credenciais do banco não pertencem ao código entregue ao navegador.
O que é uma API?
API é um contrato de comunicação entre sistemas. Uma API HTTP define endereços, métodos, dados de entrada, respostas e erros.
GET /api/tasks → listar tarefas
POST /api/tasks → criar tarefa
GET /api/tasks/42 → obter tarefa 42
API não é sinônimo de banco. A API protege regras, autenticação, autorização e formato dos dados antes de consultar ou alterar o armazenamento.
O que é uma requisição?
Requisição é a mensagem enviada pelo cliente. Ela pode conter:
- método, como GET ou POST;
- URL;
- headers;
- query parameters;
- corpo;
- cookies ou credenciais permitidas.
O que é uma resposta?
Resposta é a mensagem devolvida pelo servidor. Ela possui status, headers e, muitas vezes, um corpo em JSON.
HTTP/1.1 200 OK
Content-Type: application/json
[{ "id": 1, "title": "Estudar HttpClient" }]
O que é JSON?
JSON é um formato textual de dados. Ele parece um objeto JavaScript, mas é texto transportado pela rede e segue regras próprias. Ao receber JSON, precisamos tratar seu conteúdo como dado externo.
O que são status HTTP?
Status resume o resultado da requisição:
| Faixa | Significado geral | Exemplos |
|---|---|---|
| 2xx | sucesso | 200, 201, 204 |
| 4xx | problema na requisição ou permissão | 400, 401, 403, 404, 409 |
| 5xx | falha no servidor | 500, 503 |
Status não substitui uma mensagem estruturada. O front-end deve mapear códigos para orientações compreensíveis.
O que é HttpClient?
É o serviço Angular para enviar requisições HTTP. Ele oferece métodos tipados, tratamento de erros, interceptors e ferramentas de teste.
private readonly http = inject(HttpClient);
list(): Observable<readonly TaskDto[]> {
return this.http.get<readonly TaskDto[]>("/api/tasks");
}
O que é RxJS?
RxJS é uma biblioteca para trabalhar com fluxos assíncronos. O HttpClient retorna
um Observable: um objeto que descreve valores que poderão chegar no tempo.
Observable da requisição
├── next: resposta chegou
├── error: requisição falhou
└── complete: fluxo terminou
Observable é a resposta?
Não. Ele é uma descrição do fluxo. A requisição do HttpClient normalmente começa
quando alguém se inscreve com subscribe, AsyncPipe, toSignal ou outro consumidor.
const tasks$ = http.get<TaskDto[]>("/api/tasks"); // descreve
tasks$.subscribe(...); // executa
Observable é igual a Promise?
Não. Promise representa um resultado futuro único e começa sua execução ao ser criada. Observable pode emitir zero, um ou vários valores, é lazy em muitos casos, pode ser transformado por operadores e pode ser cancelado pela desinscrição.
Para requisições comuns do HttpClient, geralmente recebemos uma resposta e depois o
Observable completa.
Como isso entra na Knowledge AI?
Substituiremos dados fixos por um TaskApi. A tela representará explicitamente:
loading → success
→ empty
→ error
Também enviaremos um CreateTaskDto por POST e trataremos a resposta sem confundir
tipagem TypeScript com validação real.
Objetivos
Ao final da aula, o aluno deverá conseguir:
- explicar HTTP, API, cliente, servidor, requisição e resposta;
- identificar método, URL, status, headers e corpo;
- diferenciar serviço Angular e API NestJS;
- configurar e injetar
HttpClient; - criar métodos GET e POST tipados;
- explicar Observable, Observer, Subscription e operadores;
- reconhecer Observable frio e múltiplas requisições;
- transformar fluxos com
map,switchMap,startWithecatchError; - converter Observable em signal com
toSignal; - representar loading, success, empty e error;
- cancelar requisições obsoletas;
- tratar
HttpErrorResponsesem expor detalhes internos; - reconhecer
httpResourcepara leituras reativas; - validar respostas externas em ambiente de execução quando necessário.
Pré-requisitos
- Aulas 4.1 a 4.6 concluídas;
- serviços, injeção de dependência e signals;
- interfaces TypeScript e unions discriminadas;
- formulários e DTOs;
- noções de Web, HTTP e JSON dos módulos anteriores.
Pergunta orientadora
Como conectar a interface a um servidor sem esconder latência, falhas ou contratos externos?
Roteiro sugerido
| Etapa | Duração |
|---|---|
| HTTP e contrato da API | 25 min |
| HttpClient e DTOs | 25 min |
| Observable e operadores | 30 min |
| Estados e erros | 20 min |
| Laboratório | 15 min |
| Revisão | 5 min |
1. Arquitetura desta etapa
TaskPage
↓ chama
TaskApi (serviço Angular)
↓ HttpClient
/api/tasks (API NestJS)
↓ Prisma
MySQL
Cada camada tem responsabilidade própria. O componente apresenta, o serviço isola HTTP, a API valida e o banco persiste.
2. Métodos HTTP principais
| Método | Intenção comum | Exemplo |
|---|---|---|
| GET | ler | listar tarefas |
| POST | criar ou executar comando | cadastrar tarefa |
| PUT | substituir representação | atualizar tarefa completa |
| PATCH | alterar parcialmente | mudar apenas status |
| DELETE | remover | excluir tarefa |
O significado real depende do contrato da API. Não escolha método apenas pelo nome da função no front-end.
3. Status relevantes
200 OK → leitura ou atualização com body
201 Created → recurso criado
204 No Content → sucesso sem body
400 Bad Request → formato inválido
401 Unauthorized → autenticação ausente ou inválida
403 Forbidden → autenticado, mas sem permissão
404 Not Found → recurso inexistente
409 Conflict → conflito, como versão ou duplicidade
422 Unprocessable Content → dados não atendem regras
500 Internal Server Error → falha inesperada do servidor
401 e 403 não são a mesma coisa. Uma lista vazia com 200 também não é erro 404.
4. Configuração do cliente
Em Angular 21+, HttpClient está disponível por padrão. Quando precisamos configurar
recursos, usamos:
import { provideHttpClient } from "@angular/common/http";
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()],
};
Manteremos a configuração explícita para preparar interceptors na Aula 4.9.
5. Serviço de acesso a dados
import { HttpClient } from "@angular/common/http";
import { Service, inject } from "@angular/core";
@Service()
export class TaskApi {
private readonly http = inject(HttpClient);
private readonly baseUrl = "/api/tasks";
}
Evite espalhar URLs e chamadas HTTP por componentes.
6. DTO de resposta
export interface TaskDto {
readonly id: number;
readonly title: string;
readonly status: "todo" | "doing" | "done";
readonly createdAt: string;
}
DTO representa o contrato transportado. Ele pode ser diferente da entidade de banco e do modelo visual.
7. DTO de criação
export interface CreateTaskDto {
readonly title: string;
readonly description: string;
readonly priority: "low" | "medium" | "high";
readonly dueDate: string | null;
}
Não envie id, autor ou timestamps se o servidor deve defini-los.
8. GET tipado
list(): Observable<readonly TaskDto[]> {
return this.http.get<readonly TaskDto[]>(this.baseUrl);
}
O método não chama subscribe. Ele devolve o fluxo para o consumidor decidir como
compor, cancelar e apresentar.
9. POST tipado
create(input: CreateTaskDto): Observable<TaskDto> {
return this.http.post<TaskDto>(this.baseUrl, input);
}
Objetos simples são serializados como JSON. A API precisa responder com o recurso criado conforme o contrato.
10. Tipo genérico não valida JSON
http.get<TaskDto[]>("/api/tasks")
TaskDto[] é uma afirmação para o compilador. Se o servidor devolver estrutura
diferente, o Angular não valida automaticamente. Para fronteiras críticas, leia como
unknown e use um parser ou schema em ambiente de execução.
http.get<unknown>("/api/tasks").pipe(map(parseTaskList))
11. O que é um Observer?
Observer é o consumidor das notificações:
tasks$.subscribe({
next: (tasks) => console.log(tasks),
error: (error) => console.error(error),
complete: () => console.log("fim"),
});
next, error e complete são canais diferentes. Depois de error ou complete, não
há novas emissões naquele fluxo.
12. O que é uma Subscription?
É o vínculo criado por subscribe. Cancelá-lo interrompe o consumo e, em uma
requisição HTTP em andamento, normalmente aborta a operação.
const subscription = tasks$.subscribe(...);
subscription.unsubscribe();
Prefira AsyncPipe, toSignal ou takeUntilDestroyed para gerenciar ciclo de vida.
13. Observable frio
Observables do HttpClient são frios: cada inscrição independente envia nova
requisição.
const tasks$ = api.list();
tasks$.subscribe(); // GET 1
tasks$.subscribe(); // GET 2
Não inscreva várias vezes por acidente. Compartilhamento e cache exigem uma decisão explícita, não uma suposição.
14. O que é pipe()?
pipe aplica operadores em sequência:
api.list().pipe(
map((tasks) => tasks.filter((task) => task.status !== "done")),
catchError((error) => of([])),
);
Cada operador recebe um Observable e devolve outro. O fluxo original não é alterado.
15. map
Transforma cada valor emitido:
map((tasks) => ({
tasks,
total: tasks.length,
}))
Não confunda map do RxJS, que transforma emissões, com Array.map, que transforma
itens de um array. Eles podem aparecer juntos.
16. startWith
Emite um valor inicial antes da resposta:
startWith({ status: "loading" } as const)
Isso permite que a interface mostre carregamento imediatamente após a inscrição.
17. catchError
Intercepta o canal de erro e precisa devolver outro Observable:
catchError((error: HttpErrorResponse) =>
of({ status: "error", message: mapHttpError(error) } as const),
)
Retornar um estado para a UI encerra aquele erro de forma controlada. Não esconda o problema devolvendo lista vazia: vazio e falha têm significados diferentes.
18. switchMap
Troca para um novo fluxo e cancela o anterior quando chega nova emissão:
reload$.pipe(
switchMap(() => taskApi.list()),
)
É útil em busca, filtros e recarregamento. Uma resposta antiga não deve sobrescrever uma solicitação mais recente.
19. finalize
Executa ao completar, falhar ou cancelar:
finalize(() => loading.set(false))
É útil para limpeza. Para estados mais ricos, uma union discriminada evita combinar booleans incompatíveis.
20. Estado assíncrono como union
type TaskLoadState =
| { status: "loading" }
| { status: "success"; tasks: readonly TaskDto[] }
| { status: "empty" }
| { status: "error"; message: string };
Isso impede estados como loading=true e error=true ao mesmo tempo.
21. Fluxo completo de carregamento
private readonly reload$ = new Subject<void>();
private readonly state$ = this.reload$.pipe(
startWith(undefined),
switchMap(() =>
this.taskApi.list().pipe(
map((tasks): TaskLoadState =>
tasks.length > 0
? { status: "success", tasks }
: { status: "empty" },
),
startWith({ status: "loading" } as TaskLoadState),
catchError((error) =>
of({ status: "error", message: mapHttpError(error) } as TaskLoadState),
),
),
),
);
Cada recarregamento cancela a requisição anterior e produz uma sequência de estados.
22. Observable para signal
protected readonly state = toSignal(this.state$, {
initialValue: { status: "loading" } as TaskLoadState,
});
toSignal se inscreve e encerra a inscrição com o contexto Angular. Crie uma vez e
reutilize; chamar repetidamente cria novas subscriptions.
23. Template por estado
@switch (state().status) {
@case ("loading") {
<p role="status">Carregando tarefas...</p>
}
@case ("empty") {
<p>Nenhuma tarefa cadastrada.</p>
}
@case ("error") {
<p role="alert">{{ state().message }}</p>
<button type="button" (click)="reload()">Tentar novamente</button>
}
@case ("success") {
<app-task-list [tasks]="state().tasks" />
}
}
Não deixe uma tela vazia enquanto a rede trabalha ou falha.
24. Mapeando erros HTTP
function mapHttpError(error: HttpErrorResponse): string {
if (error.status === 0) return "Não foi possível conectar ao servidor.";
if (error.status === 404) return "O recurso solicitado não foi encontrado.";
if (error.status >= 500) return "O servidor encontrou um problema. Tente novamente.";
return "Não foi possível concluir a operação.";
}
Registre detalhes técnicos em observabilidade apropriada, mas não exponha stack trace, SQL ou informação sensível ao usuário.
25. Rede, timeout e servidor
HttpErrorResponse representa:
- falha de rede ou conexão, normalmente status 0;
- timeout configurado;
- resposta de erro enviada pelo back-end.
Identifique a categoria antes de decidir se retry faz sentido.
26. Retry consciente
Repetir automaticamente pode ajudar em falhas transitórias de leitura. Não aplique retry cego em POST: a primeira tentativa pode ter sido processada, causando duplicidade.
Use idempotência, limites, atraso e regras de negócio quando houver repetição.
27. Cancelamento
Desinscrever de uma requisição em andamento aborta o trabalho do cliente. switchMap
cancela solicitações antigas quando uma nova entrada chega. Isso evita corrida de
respostas em buscas rápidas.
O servidor ainda precisa tratar cancelamento e idempotência conforme sua arquitetura.
28. AsyncPipe
@if (tasks$ | async; as tasks) {
<app-task-list [tasks]="tasks" />
}
O pipe gerencia inscrição e limpeza no template. É uma boa escolha quando o fluxo já tem a forma exata de apresentação.
29. takeUntilDestroyed
Para subscriptions imperativas:
taskApi.create(dto).pipe(
takeUntilDestroyed(this.destroyRef),
).subscribe({
next: (task) => this.handleCreated(task),
error: (error) => this.handleCreateError(error),
});
O operador encerra a inscrição quando o contexto é destruído. Se chamado fora do
contexto de injeção, forneça DestroyRef explicitamente.
30. POST e estado de formulário
create(dto: CreateTaskDto): void {
this.saving.set(true);
this.taskApi.create(dto).pipe(
finalize(() => this.saving.set(false)),
takeUntilDestroyed(this.destroyRef),
).subscribe({
next: (task) => this.created.emit(task),
error: (error) => this.saveError.set(mapHttpError(error)),
});
}
Desabilite envio enquanto salva, preserve valores em erro e incorpore a resposta real do servidor ao estado.
31. URL e ambientes
Evite strings duplicadas como http://localhost:3000. Use configuração de ambiente,
proxy de desenvolvimento ou URL relativa:
private readonly baseUrl = "/api/tasks";
Segredos nunca entram em arquivos de ambiente do front-end: qualquer valor empacotado é visível no navegador.
32. CORS não é autorização
CORS controla se o navegador permite leitura de uma resposta entre origens. Ele não protege a API contra outros clientes. Autenticação e autorização continuam no servidor.
Não “corrija” CORS desabilitando toda proteção indiscriminadamente.
33. httpResource no Angular 22
Para leituras reativas, o Angular oferece httpResource:
readonly tasksResource = httpResource<readonly TaskDto[]>(() => "/api/tasks");
Ele expõe valor, loading e erro como signals e cancela a requisição anterior quando
dependências mudam. Diferentemente do HttpClient, inicia a leitura de forma eager.
Use HttpClient diretamente para POST, PUT, PATCH e DELETE. A documentação recomenda
não usar httpResource para mutações.
34. Validando resposta com httpResource
readonly tasksResource = httpResource(() => "/api/tasks", {
parse: taskListSchema.parse,
});
O parse transforma e valida dados externos. A biblioteca de schema é uma decisão do
projeto; não duplique contratos sem estratégia de manutenção.
35. Laboratório guiado
Abra o laboratório de requisições.
Etapa 1 — Resposta de sucesso
Envie GET e acompanhe requisição, loading, resposta 200 e success.
Etapa 2 — Lista vazia
Compare 200 [] com um erro. O estado correto é empty.
Etapa 3 — Erros diferentes
Simule 404, 500 e falha de rede. Observe mensagens adequadas.
Etapa 4 — Cancele uma resposta antiga
Dispare outra requisição antes da primeira terminar. O comportamento simula
switchMap.
Etapa 5 — Envie POST
Crie uma tarefa e observe method, corpo, status 201 e resposta.
Etapa 6 — Examine a fonte
Compare serviço, fluxo RxJS, template e httpResource.
36. Sobre o laboratório estático
O laboratório simula uma API para abrir sem NestJS ou rede. Latência, status e falhas
são controlados localmente. A pasta src/app contém a implementação Angular
equivalente com HttpClient, RxJS e toSignal.
37. Erros comuns
Fazer HTTP diretamente no componente
URLs, DTOs e tratamento ficam espalhados e difíceis de testar.
Achar que o Observable já executou
Sem consumidor, uma requisição HttpClient não é enviada.
Inscrever várias vezes
Cada inscrição em um Observable frio dispara outra requisição.
Transformar qualquer erro em array vazio
A interface mente ao dizer que não há dados quando a rede falhou.
Confiar no generic TypeScript
O JSON real não é validado pelo compilador.
Não representar loading
A interface parece travada durante a latência.
Deixar resposta antiga vencer
Buscas ou filtros exibem dados de uma requisição obsoleta.
Repetir POST automaticamente
Pode criar operações duplicadas.
Expor erro interno
Stack trace, SQL e detalhes de infraestrutura não pertencem à UI.
38. Boas práticas
- isole HTTP em serviços;
- use DTOs separados por operação;
- valide dados externos em ambiente de execução quando crítico;
- modele estados assíncronos explicitamente;
- diferencie vazio de erro;
- use operadores com propósito claro;
- cancele trabalho obsoleto;
- gerencie subscriptions com
AsyncPipe,toSignaloutakeUntilDestroyed; - mapeie mensagens seguras;
- aplique retry apenas quando seguro;
- mantenha URLs configuráveis;
- preserve autenticação e autorização no servidor;
- nunca exponha credenciais do MySQL.
39. Exercícios
Exercício 1 — Contrato
Crie DTOs distintos para leitura e criação de usuário.
Exercício 2 — Serviço
Implemente GET e POST em um serviço Angular.
Exercício 3 — Estado
Modele loading, success, empty e error com union discriminada.
Exercício 4 — Erros
Mapeie status 0, 404 e 500 para mensagens diferentes.
Exercício 5 — Cancelamento
Use switchMap em uma busca que depende de texto digitado.
40. Desafio
Crie uma tela de produtos que:
- use serviço de API;
- tenha DTOs de leitura e criação;
- faça GET com filtro por query parameter;
- faça POST validado;
- use
switchMappara cancelar busca anterior; - represente quatro estados;
- diferencie 200 vazio e 404;
- trate status 0 e 500;
- preserve formulário após erro;
- valide resposta crítica em ambiente de execução;
- não repita mutações automaticamente;
- possua mensagens acessíveis.
41. Lista de verificação de conclusão
- Explico cliente, servidor, requisição e resposta.
- Identifico método, URL, status, headers e corpo.
- Diferencio Angular service e API NestJS.
- Configuro e injeto
HttpClient. - Crio GET e POST tipados.
- Explico Observable e Subscription.
- Sei que cada inscrição pode enviar nova requisição.
- Uso operadores básicos conscientemente.
- Modelo loading, success, empty e error.
- Cancelo requisições obsoletas.
- Trato
HttpErrorResponse. - Sei que tipos não validam JSON em ambiente de execução.
- Reconheço quando usar
httpResource.
42. Critérios de avaliação
| Critério | Pontos |
|---|---|
| Contrato HTTP e DTOs | 20 |
| Serviço com HttpClient | 20 |
| Composição RxJS | 20 |
| Estados assíncronos | 20 |
| Erros e cancelamento | 15 |
| Acessibilidade e clareza | 5 |
| Total | 100 |
Resumo
Nesta aula, aprendemos que:
- HTTP troca requisições e respostas entre cliente e servidor;
- API NestJS protege o acesso ao MySQL;
HttpClientisola a comunicação no Angular;- métodos retornam Observables frios;
- cada inscrição pode disparar uma nova requisição;
- operadores transformam e coordenam fluxos;
switchMapcancela trabalho obsoleto;catchErrorconverte falhas em estados compreensíveis;toSignalconecta RxJS à interface baseada em signals;- vazio e erro são estados diferentes;
- generics TypeScript não validam JSON em ambiente de execução;
httpResourceé útil para GET reativo, não para mutações.
Próxima aula
Na Aula 4.8, aprofundaremos signals e arquitetura de estado da interface, incluindo estado fonte, derivado, efeitos e sincronização com dados remotos.