Engenharia de IA Aplicada à Programação Web
Aula 7 de 10
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 4.1 Introdução ao Angular e componentes
  2. 4.2 Templates, bindings e control flow
  3. 4.3 Composição, inputs e outputs
  4. 4.4 Serviços e injeção de dependência
  5. 4.5 Rotas, layouts e navegação
  6. 4.6 Formulários e validação
  7. 4.7 HTTP, APIs e RxJS
  8. 4.8 Signals e estado da interface
  9. 4.9 Interceptors, erros e acessibilidade
  10. 4.10 Testes, compilação e projeto final

Módulo 4 · Aula 4.7

HTTP, APIs e RxJS

2 horas Teoria + laboratório Angular
Ver fonte Markdown

Identificação

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:

  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

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, 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