# Aula 4.7 - HTTP, APIs e RxJS

## 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`](../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.

```text
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.

```text
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.

```text
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
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.

```ts
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.

```text
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.

```ts
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:

```text
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:

1. explicar HTTP, API, cliente, servidor, requisição e resposta;
2. identificar método, URL, status, headers e corpo;
3. diferenciar serviço Angular e API NestJS;
4. configurar e injetar `HttpClient`;
5. criar métodos GET e POST tipados;
6. explicar Observable, Observer, Subscription e operadores;
7. reconhecer Observable frio e múltiplas requisições;
8. transformar fluxos com `map`, `switchMap`, `startWith` e `catchError`;
9. converter Observable em signal com `toSignal`;
10. representar loading, success, empty e error;
11. cancelar requisições obsoletas;
12. tratar `HttpErrorResponse` sem expor detalhes internos;
13. reconhecer `httpResource` para leituras reativas;
14. 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

```text
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

```text
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:

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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.

```ts
http.get<unknown>("/api/tasks").pipe(map(parseTaskList))
```

## 11. O que é um Observer?

Observer é o consumidor das notificações:

```ts
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.

```ts
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.

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
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

```ts
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

```ts
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

```ts
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

```html
@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

```ts
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`

```html
@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:

```ts
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

```ts
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:

```ts
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`:

```ts
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`

```ts
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](../exemplos/aula-4.7/index.html).

### 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`, `toSignal` ou `takeUntilDestroyed`;
- 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 `switchMap` para 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;
- `HttpClient` isola a comunicação no Angular;
- métodos retornam Observables frios;
- cada inscrição pode disparar uma nova requisição;
- operadores transformam e coordenam fluxos;
- `switchMap` cancela trabalho obsoleto;
- `catchError` converte falhas em estados compreensíveis;
- `toSignal` conecta 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.

## Fontes oficiais

- [Visão geral do HttpClient](https://angular.dev/guide/http)
- [Configuração do HttpClient](https://angular.dev/guide/http/setup)
- [Criação de requisições](https://angular.dev/guide/http/making-requests)
- [RxJS e signals](https://angular.dev/ecosystem/rxjs-interop)
- [`takeUntilDestroyed`](https://angular.dev/ecosystem/rxjs-interop/take-until-destroyed)
- [`httpResource`](https://angular.dev/guide/http/http-resource)
- [Observable no RxJS](https://rxjs.dev/guide/observable)
