En esta página
Routing avanzado: guards, resolvers y layouts
Sistema de routing en Angular
El router de Angular mapea URLs a componentes. En Angular 21, las rutas se definen como un array de objetos Route en un archivo dedicado.
Configurar el router
En app.config.ts, provee el router con las opciones necesarias:
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withComponentInputBinding(), // Parametros como inputs
),
],
};Lazy loading
El lazy loading carga componentes solo cuando el usuario navega a su ruta:
// Un solo componente
{
path: 'about',
loadComponent: () => import('./about').then(m => m.About),
}
// Grupo de rutas (archivo separado)
{
path: 'admin',
loadChildren: () => import('./admin/routes').then(m => m.adminRoutes),
}Parametros de ruta
Con withComponentInputBinding(), los parametros de ruta se inyectan como inputs:
// Ruta: /cursos/:slug
@Component({ /* ... */ })
export class CursoDetalle {
// El router inyecta el valor del parametro :slug
readonly slug = input.required<string>();
}También puedes acceder a query params:
// URL: /buscar?q=angular
@Component({ /* ... */ })
export class Buscar {
readonly q = input<string>(''); // query param "q"
}Guards funcionales
Los guards protegen rutas. En Angular 21 se implementan como funciones simples:
function authGuard() {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.estaAutenticado()) return true;
return router.createUrlTree(['/login']);
}
// Uso en la ruta
{ path: 'dashboard', canActivate: [authGuard], /* ... */ }Tipos de guards disponibles:
| Guard | Cuando se ejecuta |
|---|---|
canActivate |
Antes de activar la ruta |
canDeactivate |
Antes de salir de la ruta |
canMatch |
Antes de intentar coincidir la ruta |
resolve |
Antes de renderizar, para pre-cargar datos |
Resolvers
Los resolvers cargan datos antes de que el componente se renderice:
function cursoResolver(route: ActivatedRouteSnapshot) {
const cursos = inject(CursosService);
const slug = route.paramMap.get('slug')!;
return cursos.obtenerPorSlug(slug);
}
// En la ruta
{
path: ':slug',
loadComponent: () => import('./detalle').then(m => m.Detalle),
resolve: { curso: cursoResolver },
}El dato resuelto se inyecta como input en el componente:
@Component({ /* ... */ })
export class Detalle {
readonly curso = input.required<Curso>(); // Inyectado por el resolver
}Layouts anidados
Usa <router-outlet> dentro de un componente layout para crear jerarquias:
// dashboard-layout.ts
@Component({
selector: 'app-dashboard-layout',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<aside><nav>...</nav></aside>
<main><router-outlet /></main>
`,
})
export class DashboardLayout {}routerLinkActive
Agrega una clase CSS cuando el link coincide con la ruta activa:
<a routerLink="/cursos" routerLinkActive="activo">Cursos</a>Práctica
- Configura lazy loading: Crea dos rutas con
loadComponentque carguen componentes de forma perezosa. Verifica en DevTools (Network) que los chunks se cargan solo al navegar. - Implementa un guard funcional: Crea un
authGuardque verifique si un signalestaAutenticadoestrue. Si no lo es, redirige a/loginusandorouter.createUrlTree(). - Crea un layout anidado: Implementa un componente layout con sidebar y
<router-outlet>. Define rutas hijas que se rendericen dentro del layout y usarouterLinkActivepara resaltar el enlace activo.
En la siguiente leccion aprenderemos sobre formularios reactivos para manejar entradas de usuario con validación.
import { ActivatedRouteSnapshot, Router, Routes } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
// --- Guard funcional ---
function authGuard() {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.estaAutenticado()) return true;
return router.createUrlTree(['/login']);
}
function adminGuard() {
const auth = inject(AuthService);
return auth.esAdmin();
}
// --- Resolver funcional ---
function cursoResolver(route: ActivatedRouteSnapshot) {
const cursos = inject(CursosService);
return cursos.obtenerPorSlug(route.paramMap.get('slug')!);
}
// --- Rutas de la app ---
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./home').then(m => m.Home),
},
{
path: 'cursos',
children: [
{
path: '',
loadComponent: () =>
import('./catálogo').then(m => m.Catalogo),
},
{
path: ':slug',
loadComponent: () =>
import('./curso-detalle').then(m => m.CursoDetalle),
resolve: { curso: cursoResolver },
},
],
},
{
path: 'dashboard',
canActivate: [authGuard],
loadComponent: () =>
import('./dashboard/layout').then(m => m.DashboardLayout),
children: [
{ path: '', redirectTo: 'resumen', pathMatch: 'full' },
{
path: 'resumen',
loadComponent: () =>
import('./dashboard/resumen').then(m => m.Resumen),
},
{
path: 'progreso',
loadComponent: () =>
import('./dashboard/progreso').then(m => m.Progreso),
},
],
},
{
path: 'admin',
canActivate: [authGuard, adminGuard],
loadChildren: () =>
import('./admin/admin.routes').then(m => m.adminRoutes),
},
{ path: '**', redirectTo: '' },
];
Inicia sesión para guardar tu progreso