Resolvers funcionales y pre-carga de datos
Los resolvers funcionales representan la evolución moderna del sistema de resolución de datos en Angular. Mientras que los guards funcionales protegen rutas controlando el acceso, los resolvers se encargan de pre-cargar datos esenciales antes de que un componente se active, garantizando que la información necesaria esté disponible desde el primer momento.
Concepto fundamental de resolvers
Un resolver es una función que se ejecuta durante el proceso de navegación, antes de que el componente de destino se inicialice. Su propósito principal es cargar datos críticos que el componente necesita para funcionar correctamente, evitando estados de carga vacíos o spinners innecesarios.
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
export const userResolver: ResolveFn<User> = (route, state) => {
const userService = inject(UserService);
const userId = route.paramMap.get('id')!;
return userService.getUser(userId);
};
La función resolver recibe dos parámetros: el ActivatedRouteSnapshot que contiene información sobre la ruta activa, y el RouterStateSnapshot que proporciona el estado completo del router.
Diferencias clave con guards funcionales
Aunque ambos son funciones que participan en el ciclo de navegación, tienen responsabilidades distintas:
Guards funcionales:
- Controlan si una ruta puede activarse
- Retornan
boolean
,UrlTree
oObservable/Promise
de estos tipos - Se ejecutan para autorización y validación
- Pueden redirigir o bloquear la navegación
Resolvers funcionales:
- Cargan datos necesarios para la ruta
- Retornan los datos directamente o
Observable/Promise
de los datos - Se ejecutan para pre-carga de información
- La navegación espera hasta que los datos estén disponibles
Configuración en rutas
Los resolvers se configuran en el objeto de rutas usando la propiedad resolve
:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'user/:id',
loadComponent: () => import('./user-detail/user-detail.component'),
resolve: {
user: userResolver,
permissions: userPermissionsResolver
}
},
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component'),
resolve: {
stats: dashboardStatsResolver
}
}
];
Cada clave en el objeto resolve
se convierte en una propiedad disponible en el ActivatedRoute.data
del componente.
Acceso a datos resueltos en componentes
Los componentes acceden a los datos pre-cargados a través del servicio ActivatedRoute
:
import { Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user-detail',
standalone: true,
template: `
<div class="user-profile">
<h1>{{ user().name }}</h1>
<p>Email: {{ user().email }}</p>
<div class="permissions">
@for (permission of permissions(); track permission.id) {
<span class="badge">{{ permission.name }}</span>
}
</div>
</div>
`
})
export class UserDetailComponent implements OnInit {
private route = inject(ActivatedRoute);
// Datos disponibles inmediatamente
user = signal<User>(null!);
permissions = signal<Permission[]>([]);
ngOnInit() {
// Los datos ya están resueltos y disponibles
this.user.set(this.route.snapshot.data['user']);
this.permissions.set(this.route.snapshot.data['permissions']);
}
}
Resolver con manejo de errores
Los resolvers deben incluir estrategias de error para manejar fallos en la carga de datos:
export const productResolver: ResolveFn<Product | null> = (route, state) => {
const productService = inject(ProductService);
const productId = route.paramMap.get('id')!;
return productService.getProduct(productId).pipe(
catchError((error) => {
console.error('Error loading product:', error);
// Retornar valor por defecto o redirigir
return of(null);
})
);
};
Cuando un resolver retorna un valor de fallback, el componente debe estar preparado para manejar estos casos especiales:
ngOnInit() {
const product = this.route.snapshot.data['product'];
if (!product) {
// Manejar caso de producto no encontrado
this.router.navigate(['/products']);
return;
}
this.product.set(product);
}
Resolver con datos en caché
Para optimizar el rendimiento, los resolvers pueden implementar estrategias de caché:
export const categoriesResolver: ResolveFn<Category[]> = () => {
const categoryService = inject(CategoryService);
// Verificar si ya tenemos datos en caché
if (categoryService.hasCategories()) {
return categoryService.getCachedCategories();
}
// Cargar y cachear datos
return categoryService.loadCategories().pipe(
tap(categories => categoryService.cacheCategories(categories))
);
};
Cuándo usar resolvers vs carga en componentes
Usa resolvers cuando:
- Los datos son críticos para la funcionalidad del componente
- Quieres evitar estados de carga vacíos
- Necesitas datos para decidir qué mostrar
- Los datos son relativamente rápidos de obtener
Carga en componentes cuando:
- Los datos no son críticos inicialmente
- Quieres mostrar la interfaz inmediatamente
- Los datos pueden tardar mucho en cargar
- Necesitas indicadores de progreso granulares
Los resolvers funcionales proporcionan una forma elegante y moderna de pre-cargar datos esenciales, mejorando significativamente la experiencia del usuario al eliminar pantallas de carga innecesarias y garantizar que los componentes tengan toda la información necesaria desde el momento de su activación.
Combinación con signals y resource()
Los resolvers modernos se potencian enormemente cuando se combinan with signals y la resource() API, creando un sistema reactivo que maneja automáticamente los estados de carga, éxito y error mientras mantiene la pre-carga de datos característica de los resolvers.
Resolvers reactivos con resource()
La resource() API permite crear resolvers que no solo cargan datos, sino que proporcionan un estado reactivo completo que los componentes pueden consumir inmediatamente:
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { resource } from '@angular/core';
import { ProductService } from './product.service';
export const productResourceResolver: ResolveFn<any> = (route, state) => {
const productService = inject(ProductService);
const productId = route.paramMap.get('id')!;
return resource({
request: () => ({ id: productId }),
loader: ({ request }) => productService.getProduct(request.id)
});
};
Este resolver retorna un resource signal que el componente puede usar directamente para acceder a estados de loading, data y error.
Acceso reactivo en componentes
Los componentes reciben recursos completamente reactivos que eliminan la necesidad de manejar estados manualmente:
import { Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product-detail',
standalone: true,
template: `
<div class="product-container">
@if (productResource.loading()) {
<div class="loading-spinner">Cargando producto...</div>
} @else if (productResource.error()) {
<div class="error-message">
Error: {{ productResource.error() }}
<button (click)="productResource.reload()">Reintentar</button>
</div>
} @else if (productResource.value()) {
<div class="product-details">
<h1>{{ productResource.value().name }}</h1>
<p>{{ productResource.value().description }}</p>
<span class="price">${{ productResource.value().price }}</span>
</div>
}
</div>
`
})
export class ProductDetailComponent implements OnInit {
private route = inject(ActivatedRoute);
productResource!: any;
ngOnInit() {
this.productResource = this.route.snapshot.data['product'];
}
}
Combinando múltiples resources en resolvers
Los resolvers pueden coordinar múltiples resources para cargar datos relacionados de forma eficiente:
export const userDashboardResolver: ResolveFn<any> = (route, state) => {
const userService = inject(UserService);
const statsService = inject(StatsService);
const userId = route.paramMap.get('id')!;
const userResource = resource({
request: () => ({ userId }),
loader: ({ request }) => userService.getUser(request.userId)
});
const statsResource = resource({
request: () => ({ userId }),
loader: ({ request }) => statsService.getUserStats(request.userId)
});
return {
user: userResource,
stats: statsResource
};
};
El componente recibe ambos resources y puede reaccionar independientemente a cada uno:
@Component({
template: `
<div class="dashboard">
@if (userResource.loading() || statsResource.loading()) {
<div class="loading">Cargando dashboard...</div>
} @else {
<header>
@if (userResource.value()) {
<h1>Bienvenido, {{ userResource.value().name }}</h1>
}
</header>
<section class="stats">
@if (statsResource.value()) {
<div class="stat-card">
<span>Total ventas</span>
<strong>{{ statsResource.value().totalSales }}</strong>
</div>
}
</section>
}
</div>
`
})
export class UserDashboardComponent implements OnInit {
private route = inject(ActivatedRoute);
userResource!: any;
statsResource!: any;
ngOnInit() {
const resolvedData = this.route.snapshot.data['dashboard'];
this.userResource = resolvedData.user;
this.statsResource = resolvedData.stats;
}
}
Resource con parámetros reactivos
Los resources en resolvers pueden reaccionar a cambios de parámetros usando signals para crear dependencias reactivas:
export const categoryProductsResolver: ResolveFn<any> = (route, state) => {
const productService = inject(ProductService);
// Signal que contiene los parámetros de la ruta
const routeParams = signal({
categoryId: route.paramMap.get('categoryId')!,
page: Number(route.queryParamMap.get('page')) || 1
});
return resource({
request: () => routeParams(),
loader: ({ request }) => productService.getProductsByCategory(
request.categoryId,
request.page
)
});
};
Estados avanzados con resource signals
Los resource signals proporcionan métodos adicionales para control granular del estado:
@Component({
template: `
<div class="product-list">
<button
(click)="refreshProducts()"
[disabled]="productsResource.loading()">
@if (productsResource.loading()) {
Actualizando...
} @else {
Actualizar productos
}
</button>
@if (productsResource.value()) {
<div class="products">
@for (product of productsResource.value(); track product.id) {
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ product.price | currency }}</p>
</div>
}
</div>
}
</div>
`
})
export class ProductListComponent {
private route = inject(ActivatedRoute);
productsResource = this.route.snapshot.data['products'];
refreshProducts() {
// Recargar datos manteniendo el estado reactivo
this.productsResource.reload();
}
}
Error handling reactivo
Los resources en resolvers proporcionan manejo de errores más sofisticado que se integra naturalmente con signals:
export const orderResolver: ResolveFn<any> = (route, state) => {
const orderService = inject(OrderService);
const router = inject(Router);
return resource({
request: () => ({ orderId: route.paramMap.get('id')! }),
loader: async ({ request }) => {
try {
return await orderService.getOrder(request.orderId);
} catch (error) {
if (error.status === 404) {
router.navigate(['/orders']);
}
throw error;
}
}
});
};
Ventajas del enfoque resource + signals
Esta combinación ofrece beneficios significativos:
- Estado unificado: Loading, data y error en un solo objeto reactivo
- Recarga automática: Métodos reload() integrados sin lógica adicional
- Composabilidad: Fácil combinación de múltiples resources
- Performance: Caching automático y actualizaciones optimizadas
- Developer experience: Menos boilerplate y mejor debugging
La integración de resolvers con signals y resource() representa la evolución natural del manejo de datos en Angular, combinando la pre-carga garantizada de los resolvers con la reactividad moderna de signals y la gestión de estados automática de la resource API.

Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender el concepto y propósito de los resolvers funcionales en Angular.
- Diferenciar entre resolvers y guards funcionales en el ciclo de navegación.
- Configurar resolvers en rutas y acceder a los datos pre-cargados en componentes.
- Implementar manejo de errores y estrategias de caché en resolvers.
- Integrar resolvers con signals y la API resource() para un manejo reactivo y eficiente de datos.