💻 Exemplos: Capítulo 14: Roteamento Avançado, Lazy Loading e Router Guards
Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 14 — Roteamento com Functional Guards (CanActivateFn) e Lazy-Loading
Este exemplo apresenta a configuração moderna de rotas no Angular 18+, utilizando divisão de código por rota (Lazy-Loading com loadComponent), injeção de parâmetros de rota direto em Signals com withComponentInputBinding e proteção de acessos com Functional Guards (CanActivateFn).
1. Guards Funcionais e Definição de Rotas: src/app/app.routes.ts
// src/app/app.routes.ts
import { Routes, CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
// Simulação de serviço de autenticação
export class AuthService {
isAuthenticated = false;
userRole: 'ADMIN' | 'OPERATOR' | 'GUEST' = 'GUEST';
}
// 1. Functional Guard: canActivateFn limpo sem classes ou interfaces legadas
export const authGuard: CanActivateFn = (route, state) => {
const router = inject(Router);
// Simulação de leitura de token ou serviço
const token = localStorage.getItem('auth_token');
if (!token) {
console.warn(`[Router Guard] Acesso negado a ${state.url}. Redirecionando para login.`);
return router.parseUrl('/login?redirect=' + encodeURIComponent(state.url));
}
// Checagem de RBAC através de data/meta da rota
const requiredRole = route.data?.['requiredRole'];
const userRole = localStorage.getItem('user_role') || 'GUEST';
if (requiredRole && userRole !== requiredRole) {
console.warn('[Router Guard] Permissão insuficiente para rota administrativa.');
return router.parseUrl('/forbidden');
}
return true;
};
// 2. Configuração de Rotas com Lazy-Loading
export const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'dashboard'
},
{
path: 'login',
loadComponent: () =>
import('./pages/login/login.component').then((m) => m.LoginComponent)
},
{
path: 'dashboard',
canActivate: [authGuard],
loadComponent: () =>
import('./pages/dashboard/dashboard.component').then((m) => m.DashboardComponent)
},
{
path: 'cluster/:clusterId',
canActivate: [authGuard],
data: { requiredRole: 'ADMIN' },
loadComponent: () =>
import('./pages/cluster-detail/cluster-detail.component').then((m) => m.ClusterDetailComponent)
},
{
path: '**',
loadComponent: () =>
import('./pages/not-found/not-found.component').then((m) => m.NotFoundComponent)
}
];
2. Componente com Input Binding de Parâmetro: src/app/pages/cluster-detail/cluster-detail.component.ts
// src/app/pages/cluster-detail/cluster-detail.component.ts
import { Component, input, computed } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-cluster-detail',
standalone: true,
imports: [RouterLink],
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>
<span class="text-xs font-mono font-bold text-red-600 uppercase">Rota Protegida (ADMIN)</span>
<h2 class="text-xl font-bold text-slate-800">Detalhes do Cluster</h2>
</div>
<a routerLink="/dashboard" class="text-xs text-indigo-600 hover:underline font-semibold">
← Voltar
</a>
</div>
<!-- O parâmetro :clusterId da URL é injetado diretamente no Signal input()! -->
<div class="p-4 bg-slate-50 border rounded-xl space-y-2 text-xs">
<p><strong>ID Injetado via URL:</strong> <code class="font-mono text-indigo-600 font-bold">{{ clusterId() }}</code></p>
<p><strong>URI Canônica:</strong> {{ canonicalUri() }}</p>
<p><strong>Nível de Acesso:</strong> Autenticado com Perfil Master</p>
</div>
</div>
`
})
export class ClusterDetailComponent {
// withComponentInputBinding() injeta :clusterId diretamente nesta input reativa!
clusterId = input.required<string>();
canonicalUri = computed(() => `https://clusters.corp.internal/k8s/${this.clusterId()}`);
}
🖥️ Saída / Comportamento Esperado
Cenário 1 — Usuário Não Autenticado acessa "/dashboard":
authGuard intercepta a navegação:
localStorage.getItem('auth_token') é null.
Console: "[Router Guard] Acesso negado a /dashboard. Redirecionando para login."
A URL é redirecionada para "/login?redirect=%2Fdashboard".
Cenário 2 — Usuário Autenticado acessa "/cluster/k8s-sa-east-01":
authGuard valida o token e o perfil 'ADMIN'.
Navegação concedida.
O compilador carrega o chunk cluster-detail.component.js sob demanda (lazy-loading).
withComponentInputBinding() vincula o parâmetro 'k8s-sa-east-01' da rota diretamente no signal clusterId().
A tela exibe:
"ID Injetado via URL: k8s-sa-east-01"
"URI Canônica: https://clusters.corp.internal/k8s/k8s-sa-east-01"
🔍 Destaques da Implementação
CanActivateFnFuncional: Não requer criação de classes de serviço nem registro emproviders, reduzindo boilerplate em 70%.withComponentInputBinding(): Dispensa a injeção deActivatedRoutepara ler parâmetros simples da URL, transformando-os eminput()signals.- Divisão de Chunks Eficiente: Cada rota em
loadComponentgera um arquivo JS independente que só é baixado sob demanda.