Engenharia de IA Aplicada à Programação Web
Aula 9 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.9

Interceptors, erros e acessibilidade

2 horas Teoria + laboratório Angular
Ver fonte Markdown

Identificação

  • Duração: 2 horas
  • Tipo: teoria aplicada e laboratório
  • Entrega: fluxo HTTP resiliente e retorno acessível
  • Laboratório: ../exemplos/aula-4.9/index.html

Introdução

O que é um interceptor?

Interceptor é uma função posicionada no caminho das requisições feitas pelo HttpClient. Ela pode observar ou transformar uma requisição antes de chegar à API e observar a resposta quando ela retorna.

componente → HttpClient → interceptor → API
componente ← Observable ← interceptor ← API

Ele funciona como um middleware do cliente Angular. Não é a API NestJS, não roda no MySQL e não intercepta todas as requisições do navegador: apenas as que passam pelo HttpClient configurado naquela aplicação.

Para que serve?

Interceptors removem preocupações repetidas de cada serviço:

  • adicionar um identificador de correlação;
  • anexar credenciais permitidas ao destino correto;
  • medir duração;
  • registrar falhas técnicas;
  • aplicar timeout ou retry controlado;
  • coordenar um indicador global de atividade.

O TaskApi continua responsável pelos endpoints de tarefas. O interceptor cuida de uma política transversal que vale para várias chamadas.

Interceptor protege a API?

Não. Qualquer código Angular é entregue ao navegador e pode ser inspecionado ou alterado pelo usuário. A API NestJS precisa autenticar, autorizar e validar cada operação.

Angular adiciona credencial → NestJS verifica credencial → regra autoriza ação

Nunca coloque senha do MySQL, chave privada ou segredo de servidor em um interceptor.

O que é uma cadeia de interceptors?

Podemos registrar várias funções. Na ida, elas executam na ordem configurada. A resposta retorna pelas mesmas funções no sentido inverso.

requisição → correlação → autenticação → telemetria → API
resposta   ← correlação ← autenticação ← telemetria ← API

Cada interceptor decide se chama next(request). Sem next, a cadeia não continua, a menos que ele produza uma resposta sintética conscientemente, como em um cache.

O que é erro HTTP?

É uma falha observada no fluxo de uma requisição. O Angular a representa com HttpErrorResponse, incluindo falhas de rede, timeout, parsing e respostas como 401, 404 ou 500.

Um status descreve a categoria técnica, mas a interface precisa comunicar uma ação útil: tentar novamente, revisar dados, entrar novamente ou procurar suporte.

Erro HTTP é igual a exceção global?

Não. Um 404 esperado pertence ao fluxo da chamada e deve ser tratado perto da operação. ErrorHandler é uma última fronteira para erros inesperados que escaparam, não um substituto para catchError.

O que acessibilidade tem a ver com erros?

Uma mensagem apenas vermelha pode não ser percebida por quem não enxerga cores ou usa leitor de tela. Retorno acessível combina texto, semântica, foco e uma próxima ação clara.

<p role="status">Tarefa salva com sucesso.</p>
<p role="alert">Não foi possível salvar. Revise a conexão.</p>

role="status" anuncia atualizações comuns de forma educada. role="alert" é assertivo e deve ser usado com moderação para mensagens importantes.

Como isso entra na Knowledge AI?

O painel enviará requisições por uma cadeia previsível. O interceptor adicionará correlação e medirá a operação; o serviço preservará o erro; a página apresentará loading, sucesso ou falha com semântica acessível.

Objetivos

Ao final da aula, o aluno deverá conseguir:

  1. explicar interceptor e middleware;
  2. diferenciar interceptor Angular e middleware NestJS;
  3. criar um interceptor funcional;
  4. configurar withInterceptors;
  5. explicar a ordem de requisição e resposta;
  6. clonar requisições imutáveis;
  7. utilizar HttpContextToken para políticas por requisição;
  8. preservar erros com throwError;
  9. classificar rede, timeout, autenticação, cliente e servidor;
  10. aplicar retry apenas quando seguro;
  11. separar mensagem ao usuário de telemetria técnica;
  12. implementar loading sem contagem incorreta;
  13. usar status, alert e foco conscientemente;
  14. distinguir erro esperado de ErrorHandler global.

Pré-requisitos

  • Aulas 4.1 a 4.8 concluídas;
  • HttpClient, Observable e operadores RxJS;
  • serviços e injeção de dependência;
  • estados loading, success, empty e error;
  • HTML semântico e navegação por teclado.

Pergunta orientadora

Como aplicar políticas comuns a todas as requisições sem esconder erros nem deixar usuários sem retorno?

Roteiro sugerido

Etapa Duração
Interceptor e cadeia HTTP 25 min
Imutabilidade, headers e contexto 25 min
Erros, timeout e retry 30 min
Retorno acessível 20 min
Laboratório 15 min
Revisão 5 min

1. Forma mínima de um interceptor funcional

import { HttpInterceptorFn } from "@angular/common/http";

export const loggingInterceptor: HttpInterceptorFn = (request, next) => {
  console.log(request.method, request.url);
  return next(request);
};

request é a mensagem atual. next entrega essa mensagem ao próximo elemento da cadeia e retorna um Observable de eventos HTTP.

2. Configuração explícita

import { provideHttpClient, withInterceptors } from "@angular/common/http";

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([
        correlationInterceptor,
        authInterceptor,
        telemetryInterceptor,
      ]),
    ),
  ],
};

Interceptors funcionais são preferidos pela documentação atual por apresentarem ordem mais previsível em configurações complexas.

3. Ordem da cadeia

Com [correlation, auth, telemetry]:

ida:   correlation → auth → telemetry → backend
volta: correlation ← auth ← telemetry ← backend

Na volta, cada função observa o Observable devolvido por next.

4. Requisições são imutáveis

A maior parte de HttpRequest e HttpResponse não deve ser modificada diretamente. Use clone:

const correlatedRequest = request.clone({
  setHeaders: { "X-Correlation-ID": crypto.randomUUID() },
});

return next(correlatedRequest);

Isso torna o interceptor mais seguro quando a mesma requisição atravessa a cadeia novamente em um retry. O corpo não recebe proteção contra mutação profunda; evite alterá-lo no lugar.

5. Correlação não é autenticação

Um correlation ID liga registros do navegador, gateway e servidor para investigar uma operação. Ele não comprova identidade nem concede permissão.

X-Correlation-ID: 58fa...
Authorization: Bearer credencial-do-usuário

Cada header tem finalidade diferente.

6. Não enviar credencial para qualquer destino

const isOurApi = request.url.startsWith("/api/");
if (!isOurApi) return next(request);

Antes de anexar uma credencial, confirme destino e política. Uma URL externa para imagens ou analytics não deve receber automaticamente o token da API.

7. Exemplo de autenticação

export const authInterceptor: HttpInterceptorFn = (request, next) => {
  const session = inject(SessionService);
  const token = session.accessToken();

  if (!token || !request.url.startsWith("/api/")) {
    return next(request);
  }

  return next(request.clone({
    setHeaders: { Authorization: `Bearer ${token}` },
  }));
};

O servidor ainda deve validar token, expiração, audiência e autorização.

8. Metadados com HttpContext

Algumas políticas pertencem à aplicação, mas não devem viajar como headers.

export const SKIP_GLOBAL_ERROR = new HttpContextToken<boolean>(() => false);

http.get("/api/health", {
  context: new HttpContext().set(SKIP_GLOBAL_ERROR, true),
});

O interceptor lê request.context. Esse contexto não é enviado à API.

9. Observando a resposta

return next(request).pipe(
  tap((event) => {
    if (event.type === HttpEventType.Response) {
      console.log(event.status, request.url);
    }
  }),
);

O fluxo contém diferentes HttpEvent. Verifique o tipo antes de tratar o evento como resposta final.

10. Telemetria com finalize

export const telemetryInterceptor: HttpInterceptorFn = (request, next) => {
  const startedAt = performance.now();

  return next(request).pipe(
    finalize(() => {
      const duration = performance.now() - startedAt;
      console.info(request.method, request.url, duration);
    }),
  );
};

finalize executa após sucesso, erro ou cancelamento. Não registre corpo, tokens ou dados pessoais indiscriminadamente.

11. Classificação de falhas

Situação Indício Ação comum
rede/CORS status 0 verificar conexão e configuração
timeout erro com causa de timeout permitir nova tentativa consciente
não autenticado 401 renovar sessão ou entrar novamente
sem permissão 403 explicar limite, não insistir
não encontrado 404 contextualizar o recurso
conflito 409 atualizar dados ou resolver versão
validação 400/422 associar problemas aos campos
servidor 5xx mensagem segura e correlação

Não trate todos os 4xx ou 5xx com o mesmo texto.

12. Mapeamento para erro de aplicação

export interface AppHttpError {
  readonly kind: "network" | "auth" | "forbidden" | "not-found" | "server" | "unknown";
  readonly message: string;
  readonly correlationId: string | null;
  readonly retryable: boolean;
}

O modelo da interface não precisa expor toda a estrutura técnica de HttpErrorResponse.

13. Preserve o canal de erro

return next(request).pipe(
  catchError((error: HttpErrorResponse) => {
    telemetry.capture(sanitize(error));
    return throwError(() => error);
  }),
);

O interceptor registrou e devolveu o erro. O serviço ou componente ainda decide a mensagem e o estado da operação.

14. Quando recuperar no interceptor

Recuperar globalmente só é adequado quando existe uma resposta correta para todas as chamadas afetadas, como usar um cache válido. Transformar qualquer falha em [] faz a interface confundir erro com vazio.

15. Retry não é “tentar até funcionar”

Retry repete a operação. Ele pode ajudar em falhas transitórias, mas aumenta carga e pode duplicar mutações.

const canRetry = request.method === "GET";

return next(request).pipe(
  retry({ count: canRetry ? 2 : 0, delay: 500 }),
);

Na prática, examine o tipo de erro, aplique atraso progressivo, limite tentativas e considere jitter. POST só deve ser repetido automaticamente com uma estratégia de idempotência acordada com o servidor.

16. Timeout

http.get("/api/tasks", { timeout: 5_000 });

Timeout limita espera do cliente, mas não prova que o servidor desfez uma mutação. A operação pode ter chegado ao back-end antes de a resposta expirar.

17. Renovação de sessão

Várias requisições podem receber 401 simultaneamente. Um fluxo de renovação precisa:

  1. permitir uma renovação por vez;
  2. enfileirar ou rejeitar requisições concorrentes;
  3. limitar tentativas;
  4. evitar interceptar a própria chamada de renovação em ciclo;
  5. encerrar sessão quando a renovação falhar.

Não implemente recursão ilimitada dentro do interceptor.

18. Indicador global com contador

Um boolean falha quando duas requisições se sobrepõem:

requisição A inicia → loading=true
requisição B inicia → loading=true
requisição A termina → loading=false  ← B ainda está ativa

Use um contador:

loading.start();
return next(request).pipe(finalize(() => loading.finish()));

O serviço expõe activeRequests > 0. Garanta que cancelamento e erro também reduzam o contador.

19. Quem apresenta a mensagem?

interceptor → política técnica e telemetria
serviço     → traduz contrato para erro de aplicação
componente  → contexto, texto, foco e ação

Se todas as camadas abrirem um toast, o usuário receberá mensagens duplicadas.

20. Estados acessíveis

@switch (state().status) {
  @case ("loading") {
    <p role="status">Carregando tarefas...</p>
  }
  @case ("error") {
    <section aria-labelledby="error-title">
      <h2 id="error-title">Não foi possível carregar</h2>
      <p role="alert">{{ state().message }}</p>
      <button type="button" (click)="reload()">Tentar novamente</button>
    </section>
  }
}

O botão continua acessível por teclado e a mensagem não depende somente de cor.

21. status ou alert?

Semântica Uso
role="status" carregamento concluído, item salvo, atualização comum
role="alert" falha importante que exige atenção imediata
foco programático levar a uma região que precisa ser lida e operada

Alertas assertivos podem interromper o leitor de tela. Não use para cada pequena mudança.

22. Região viva existente

Para compatibilidade consistente, mantenha a região viva no DOM e altere seu texto:

<p role="status" aria-atomic="true">{{ announcement() }}</p>

aria-atomic="true" solicita o anúncio do conteúdo completo após a atualização.

23. Gerenciamento de foco

Depois de uma falha de envio com vários problemas, mover o foco para um resumo pode ser útil:

readonly errorSummary = viewChild<ElementRef<HTMLElement>>("errorSummary");

focusErrorSummary(): void {
  this.errorSummary()?.nativeElement.focus();
}
<section #errorSummary tabindex="-1" aria-labelledby="error-title">

Não mova foco a cada atualização automática. Preserve o contexto do usuário.

24. Mensagem deve orientar

Evite “Erro 500” como único texto. Prefira:

Não foi possível carregar as tarefas. Tente novamente. Se o problema continuar, informe o código 58FA ao suporte.

O status e o stack trace podem ir para telemetria sanitizada.

25. ErrorHandler global

ErrorHandler captura erros inesperados entregues ao mecanismo global do Angular. Use-o como última fronteira de observabilidade e recuperação segura.

@Service()
export class GlobalErrorHandler implements ErrorHandler {
  handleError(error: unknown): void {
    this.telemetry.captureUnknown(error);
  }
}

Não encaminhe todo 404 esperado ao ErrorHandler; trate-o no fluxo HTTP.

26. Privacidade e registros

Nunca registre indiscriminadamente:

  • header Authorization;
  • cookies ou tokens;
  • prompts e documentos privados;
  • dados pessoais;
  • corpo completo de formulários;
  • SQL ou stack trace na interface.

Prefira allowlist de campos, correlação e redaction.

27. Testando interceptors

Teste comportamento observável:

  • header foi adicionado apenas à API correta;
  • requisição original não foi mutada;
  • ordem configurada foi respeitada;
  • erro continuou chegando ao consumidor;
  • contador voltou a zero em sucesso, falha e cancelamento;
  • GET transitório respeitou o limite de retry;
  • POST não foi repetido sem idempotência.

Use as ferramentas de teste do HttpClient para controlar requisições e respostas.

28. Laboratório guiado

Abra o fluxo de interceptors.

Etapa 1 — Envie uma requisição com sucesso

Observe a ordem de ida e a ordem inversa da resposta.

Etapa 2 — Inspecione os headers

Veja correlação e autorização simulada. Desative autenticação e compare.

Etapa 3 — Simule rede, 401, 403, 404 e 500

Compare classificação técnica, mensagem ao usuário e ação sugerida.

Etapa 4 — Ative retry em GET

Veja uma falha transitória ser repetida até o limite. Troque para POST e observe o bloqueio do retry automático.

Etapa 5 — Compare status e alert

O laboratório identifica qual região viva anunciaria cada estado.

Etapa 6 — Examine as fontes

Compare configuração, interceptors, mapeamento e template acessível.

29. Sobre o laboratório estático

O laboratório simula a cadeia sem enviar requisições externas. A pasta src/app contém a configuração e os interceptors Angular equivalentes. Dados inseridos são renderizados com textContent, não innerHTML.

30. Erros comuns

Colocar toda regra no interceptor

Ele se torna global, acoplado e difícil de entender.

Engolir o erro

Devolver lista vazia impede a tela de diferenciar falha de ausência de dados.

Anexar token em URL externa

Credenciais podem vazar para destinos que não deveriam recebê-las.

Mutar requisição diretamente

Interceptors e retries dependem de operações idempotentes e clones previsíveis.

Retry em toda operação

POST pode ser executado duas vezes.

Um boolean para requisições concorrentes

O primeiro término esconde o loading enquanto outra chamada continua.

Alertar tudo

Muitas regiões assertivas interrompem e confundem tecnologias assistivas.

Usar somente cor

Estado precisa de texto, semântica e ação compreensível.

Mostrar detalhes técnicos

Stack trace e infraestrutura não ajudam o usuário e podem expor informação sensível.

31. Exercício de fixação

Implemente:

  1. correlation interceptor;
  2. telemetry interceptor com duração;
  3. token de contexto para pular o loading global;
  4. mapeador de 401, 403, 404 e 5xx;
  5. região de status para sucesso;
  6. resumo de erro focável com botão de nova tentativa.

Desenhe a ordem completa de requisição e resposta.

32. Desafio individual

Adicione uma política de retry que:

  • aceite apenas GET e HEAD;
  • repita apenas status 0, 502, 503 ou 504;
  • limite tentativas;
  • use atraso progressivo com jitter;
  • permita opt-out com HttpContextToken;
  • registre apenas dados sanitizados;
  • comunique a tentativa ao usuário sem alertas excessivos.

33. Lista de verificação de conclusão

  • Sei explicar interceptor sem confundi-lo com API.
  • Conheço a ordem da cadeia.
  • Clono requisições antes de alterar headers.
  • Restrinjo credenciais ao destino correto.
  • Preservo erros que não resolvi.
  • Diferencio 401 de 403.
  • Não aplico retry cego a POST.
  • Uso contador para loading concorrente.
  • Distingo status, alert e foco.
  • Não exponho detalhes sensíveis.

34. Critérios de avaliação

Critério Pontos
Cadeia e configuração corretas 20
Imutabilidade e headers seguros 15
Classificação e preservação de erros 20
Retry e concorrência conscientes 15
Retorno acessível 20
Privacidade, testes e explicação 10
Total 100

35. Resumo

  • interceptor é middleware do HttpClient;
  • funções registradas formam uma cadeia ordenada;
  • requisição e resposta devem ser clonadas para alteração;
  • políticas globais não substituem regras de domínio;
  • erros não resolvidos continuam no canal de erro;
  • retry depende de método, falha e idempotência;
  • mensagens acessíveis combinam texto, semântica, foco e ação;
  • ErrorHandler trata o inesperado, não substitui tratamento HTTP.

36. Fontes oficiais

Próxima aula

Na Aula 4.10, concluiremos o módulo com testes de componentes e serviços, compilação de produção, documentação e apresentação do painel Angular.