Capítulo 14: Roteamento Avançado (Lazy Loading, Functional Guards e Resolvers)
Capítulo 14: Roteamento Avançado (Lazy Loading, Functional Guards e Resolvers)
Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR
🗺️ Mapa Conceitual da Pipeline de Roteamento Moderno
flowchart TD
URL["Mudança de URL no Navegador (/admin/relatorios/42)"] --> Router["Angular Router (provideRouter)"]
subgraph PIPELINE["Pipeline de Decisão e Carregamento"]
G1["CanMatchFn: A rota existe e corresponde ao usuário?"]
G2["CanActivateFn: O usuário possui permissão de acesso?"]
L["loadComponent() / loadChildren(): Download Lazy do Chunk JS"]
R["ResolveFn: Pré-carregamento dos dados necessários"]
B["withComponentInputBinding: Injeta :id e data direto em input()"]
end
Router --> G1
G1 -->|Autorizado| G2
G2 -->|Autorizado| L
L --> R
R --> B
B --> TargetComponent["Componente Standalone Renderizado no <router-outlet />"]
G2 -->|Negado| Redirect["Redirecionamento para /login via UrlTree"]
style URL fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style Router fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style PIPELINE fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style TargetComponent fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style Redirect fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. O Roteador Standalone no Angular 18+
No Angular moderno, o roteador foi completamente reestruturado em torno de funções puras e injeção funcional. Classes legadas baseadas em interfaces (canActivate: [AuthGuard]) foram substituídas por guardas funcionais (canActivate: [authGuard]), eliminando arquivos de classe desnecessários e tornando a árvore de rotas muito mais legível.
1.1 Configuração Central no app.config.ts
A inicialização do roteador utiliza o provedor provideRouter acompanhado de recursos opcionais modernos:
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding, withViewTransitions, withPreloading, PreloadAllModules } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withComponentInputBinding(), // Liga parâmetros de URL (:id) diretamente a input()
withViewTransitions(), // Animações fluidas nativas da View Transitions API
withPreloading(PreloadAllModules) // Pré-carrega chunks em background após idle
)
]
};
🚀 2. Lazy Loading com loadComponent e Sub-rotas
O carregamento preguiçoso (Lazy Loading) é o pilar fundamental para garantir tempos de carregamento instantâneos e altas pontuações no Google Core Web Vitals:
// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard, adminGuard } from './core/guards/auth.guard';
import { produtoResolver } from './core/resolvers/produto.resolver';
export const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'catalogo'
},
{
path: 'catalogo',
// Carrega o componente Standalone sob demanda em um chunk JS isolado
loadComponent: () => import('./features/catalogo/catalogo.component').then(m => m.CatalogoComponent)
},
{
path: 'produto/:id',
loadComponent: () => import('./features/detalhe-produto/detalhe-produto.component').then(m => m.DetalheProdutoComponent),
resolve: {
detalhes: produtoResolver // Executa antes de exibir o componente
}
},
{
path: 'admin',
canActivate: [authGuard, adminGuard], // Proteção com guardas funcionais
// Carrega um arquivo inteiro de sub-rotas isoladas
loadChildren: () => import('./features/admin/admin.routes').then(m => m.ADMIN_ROUTES)
},
{
path: '**',
loadComponent: () => import('./features/not-found/not-found.component').then(m => m.NotFoundComponent)
}
];
🛡️ 3. Guardas Funcionais: CanActivateFn e CanDeactivateFn
As guardas funcionais utilizam inject() internamente para validar permissões, redirecionar o usuário via UrlTree ou impedir a perda de dados em formulários não salvos:
3.1 Guarda de Autenticação (CanActivateFn)
// core/guards/auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAutenticado()) {
return true; // Acesso liberado
}
// Redireciona com segurança preservando a URL de destino
return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } });
};
3.2 Guarda de Prevenção de Perda de Dados (CanDeactivateFn)
Impede que o usuário feche a aba ou navegue para fora caso tenha alterações pendentes:
import { CanDeactivateFn } from '@angular/router';
export interface FormularioPendente {
temAlteracoesNaoSalvas(): boolean;
}
export const unsavedChangesGuard: CanDeactivateFn<FormularioPendente> = (component) => {
if (component.temAlteracoesNaoSalvas()) {
return confirm('Você possui alterações não salvas. Deseja realmente sair desta página?');
}
return true;
};
📥 4. Component Input Binding (withComponentInputBinding)
Com o withComponentInputBinding(), parâmetros de rota (:id), query params (?busca=angular) e dados de resolver (resolve: { ... }) são injetados diretamente em Signal Inputs do componente, eliminando a dependência direta de ActivatedRoute:
import { Component, input } from '@angular/core';
@Component({
selector: 'app-detalhe-produto',
standalone: true,
template: `
<div class="card">
<h2>Produto ID: {{ id() }}</h2>
<p>Termo de Busca de Origem: {{ busca() }}</p>
<p>Título: {{ detalhes().nome }}</p>
</div>
`
})
export class DetalheProdutoComponent {
// Injetado automaticamente pelo Angular Router a partir da rota '/produto/:id'
id = input.required<string>();
// Injetado automaticamente a partir do query param '?busca=term'
busca = input<string>('');
// Injetado automaticamente a partir do produtoResolver
detalhes = input.required<{ id: string; nome: string; preco: number }>();
}
⚖️ 5. Comparativo: Roteamento Clássico vs Funcional
| Característica | Roteamento Clássico (Angular <15) | Roteamento Moderno (Angular 18+) |
|---|---|---|
| Configuração | RouterModule.forRoot(routes) |
provideRouter(routes, ...) |
| Definição de Guardas | Classes com @Injectable e implements CanActivate |
Funções puras tipadas (CanActivateFn) |
| Injeção de Parâmetros | Injeção manual de ActivatedRoute e snapshot |
withComponentInputBinding() via input() |
| Carregamento de Módulos | loadChildren: () => import().then(m => m.MeuModule) |
loadComponent direto para a classe Standalone |
| Animações de Página | Angular Animations complexas no template | withViewTransitions() nativo do navegador |
⚠️ 6. Armadilhas Comuns e Anti-Patterns
- Retornar
falseem vez deUrlTreeno Redirecionamento:- Problema: Retornar simplesmente
falsena guarda bloqueia a rota, mas deixa a URL e a tela travadas no limbo. - Solução: Retorne sempre
router.createUrlTree(['/destino'])para que o roteador realize o redirecionamento automático de forma atômica.
- Problema: Retornar simplesmente
- Esquecer de Adicionar
<router-outlet />:- Se o template principal não possuir a tag
<router-outlet />, os componentes roteados não encontrarão um ponto de montagem na tela.
- Se o template principal não possuir a tag
📋 7. Cheat-Sheet: Resumo Executivo
// Guarda funcional
export const minhaGuarda: CanActivateFn = () => {
return inject(AuthService).isLogado() || inject(Router).parseUrl('/login');
};
// Rota Standalone Lazy
{
path: 'dashboard',
canActivate: [minhaGuarda],
loadComponent: () => import('./dash.component').then(c => c.DashComponent)
}