💻 Exemplos: Capítulo 15: Cliente HTTP (provideHttpClient, HttpInterceptorFn e Erros)
Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 15 — Cliente HTTP Moderno com HttpInterceptorFn e Gestão de Erros
Este exemplo apresenta a configuração corporativa do HttpClient no Angular 18+, utilizando Functional Interceptors (HttpInterceptorFn) para injeção de tokens JWT Bearer e tratamento resiliente de erros HTTP com retentativas controladas e captura de falhas 401/500.
1. Interceptors Funcionais: src/app/interceptors/auth.interceptor.ts
// src/app/interceptors/auth.interceptor.ts
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
// 1. Interceptor de Autenticação Funcional
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('auth_access_token');
const correlationId = `req-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
// Clona a requisição e anexa cabeçalhos imutáveis
const modifiedReq = req.clone({
setHeaders: {
'X-Correlation-ID': correlationId,
...(token ? { Authorization: `Bearer ${token}` } : {})
}
});
return next(modifiedReq);
};
// 2. Interceptor Central de Tratamento de Erros Funcional
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const router = inject(Router);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
console.warn('[HTTP 401] Token expirado ou inválido. Expulsando para /login.');
localStorage.removeItem('auth_access_token');
router.navigate(['/login']);
} else if (error.status === 403) {
console.error('[HTTP 403] Permissão negada pelo servidor para o recurso:', req.url);
} else if (error.status >= 500) {
console.error('[HTTP 500] Erro interno no cluster de backend:', error.message);
}
return throwError(() => error);
})
);
};
2. Configuração no Bootstrap: src/main.ts
// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor, errorInterceptor } from './app/interceptors/auth.interceptor';
bootstrapApplication(AppComponent, {
providers: [
// Registro de interceptores funcionais modernos na esteira do HttpClient
provideHttpClient(
withFetch(),
withInterceptors([authInterceptor, errorInterceptor])
)
]
});
3. Componente de Consumo: src/app/components/api-catalog.component.ts
// src/app/components/api-catalog.component.ts
import { Component, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';
interface ServiceEndpoint {
id: string;
name: string;
version: string;
health: 'UP' | 'DOWN';
}
@Component({
selector: 'app-api-catalog',
standalone: true,
imports: [CommonModule],
template: `
<div class="max-w-xl mx-auto p-6 bg-white border border-slate-200 rounded-2xl shadow-sm space-y-4">
<div class="flex items-center justify-between border-b pb-3">
<div>
<h2 class="text-xl font-bold text-slate-800">🌐 Consumo com HttpInterceptorFn</h2>
<p class="text-xs text-slate-500">Injeção de Bearer Token e X-Correlation-ID transparente</p>
</div>
<button
type="button"
(click)="fetchServices()"
[disabled]="isLoading()"
class="px-3.5 py-1.5 text-xs font-semibold bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors disabled:opacity-50"
>
{{ isLoading() ? 'Consultando...' : 'Consultar API' }}
</button>
</div>
@if (errorMessage()) {
<div class="p-3 bg-red-50 border border-red-200 text-red-700 text-xs rounded-xl">
⚠️ {{ errorMessage() }}
</div>
}
<!-- Lista de Serviços Retornados -->
<div class="space-y-2">
@for (srv of services(); track srv.id) {
<div class="p-3 border rounded-xl flex justify-between items-center bg-slate-50 text-xs">
<div>
<span class="font-bold text-slate-800">{{ srv.name }}</span>
<p class="text-slate-400 font-mono text-[10px]">Versão: {{ srv.version }}</p>
</div>
<span
class="px-2 py-0.5 rounded text-[10px] font-bold"
[class.bg-emerald-100]="srv.health === 'UP'"
[class.text-emerald-800]="srv.health === 'UP'"
>
{{ srv.health }}
</span>
</div>
} @empty {
<div class="p-6 text-center text-xs text-slate-400 border border-dashed rounded-xl">
Clique em "Consultar API" para disparar a requisição com os interceptors ativos.
</div>
}
</div>
</div>
`
})
export class ApiCatalogComponent {
private http = inject(HttpClient);
services = signal<ServiceEndpoint[]>([]);
isLoading = signal<boolean>(false);
errorMessage = signal<string | null>(null);
fetchServices(): void {
this.isLoading.set(true);
this.errorMessage.set(null);
// O authInterceptor injetará os cabeçalhos Authorization e X-Correlation-ID automaticamente
this.http
.get<ServiceEndpoint[]>('https://api.empresa.com.br/v1/services')
.subscribe({
next: (data) => {
this.services.set(data);
this.isLoading.set(false);
},
error: (err) => {
this.errorMessage.set(`Falha na comunicação: ${err.message}`);
this.isLoading.set(false);
}
});
}
}
🖥️ Saída / Comportamento Esperado
Inspeção de Cabeçalhos HTTP no DevTools (Network Tab):
GET https://api.empresa.com.br/v1/services HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsIn...
X-Correlation-ID: req-1727489200-482
Caso a API responda com status HTTP 401 (Unauthorized):
errorInterceptor intercepta a falha:
Console: "[HTTP 401] Token expirado ou inválido. Expulsando para /login."
localStorage é limpo e o usuário é redirecionado para a tela de autenticação.
🔍 Destaques da Implementação
HttpInterceptorFn: Funções puras sem estado substituem a interface arcaicaHttpInterceptorcom classes e injeções múltiplasHTTP_INTERCEPTORS.withInterceptors([fn1, fn2]): Execução ordenada previsível da esteira de requisição e resposta.- Imutabilidade com
req.clone(): Garante que a requisição original nunca seja mutada acidentalmente em caso de retentativas.