Back to: Angular Tutorials For Beginners and Professionals
Route Resolvers in Angular with Real-time Application
In this post, we will discuss Route Resolvers in Angular with a Real-time Application. In real-world Angular applications, routing is not just about moving from one page to another. Very often, a page depends on data to render correctly. For example, a Product Details page may require product data, a Profile page may require user information, or an Order page may require order history. If the page loads before the data is ready, users see Loaders, Empty Screens, or Flickering UI.
Route Resolvers solve this exact problem. Route Resolvers are a Routing Feature that allows Angular to fetch required data before a route is activated, so when the component loads, the data is already available.
That means the router waits, fetches the data first, and activates the component only then, so the user doesn’t land on a “Half-Empty” page. This creates a smoother, more predictable, and more professional user experience.
What is a Route Resolver in Angular?
A Route Resolver is a special service in Angular Routing that runs before navigation completes. Its responsibility is to prepare the data required by a route and ensure it is ready before the target component is created.
In simple terms:
- Navigation starts
- Resolver runs and fetches data
- Angular waits for the resolver to finish
- Component loads with ready-to-use data
So, the component never has to worry about when data arrives. It simply reads the data and displays it.
Why Do We Need Route Resolvers?
Without resolvers, most applications load components first and then fetch data inside ngOnInit. This leads to several real-world problems:
- Components load with empty UI and update later
- Multiple loaders and spinners appear
- Components become tightly coupled with data-fetching logic
Route Resolvers fix these problems by moving data-fetching responsibility to the routing layer.
Real-World Analogy
Think of booking a hotel room:
- Without Resolver: You enter the room, and then the staff checks whether it is ready.
- With Resolver, the staff checks the room first and only then gives you the key.
Resolvers ensure users only enter a page when everything is ready.
Where Does a Resolver Fit in Angular Routing?
Angular routing works as a pipeline:
- User requests a URL
- Guards decide whether navigation is allowed
- Resolvers fetch required data
- Component is created
- View is rendered
Resolvers run after guards but before component creation.
Resolver vs Component Data Loading
Without Resolver:
- Component Loads
- API call happens
- UI updates later
With Resolver:
- API call happens first
- Component loads with ready data
- UI renders instantly
This separation keeps components clean and focused only on presentation logic.
Route Resolvers in Angular – With and Without Resolver
Without a Route Resolver, the route opens immediately, then the component loads data and displays a loader on the page. This is common, but it often creates UI flicker and forces your component to handle too many responsibilities (loading, errors, validation, navigation, data).
With a Route Resolver, Angular loads the required data before opening the page, so the details page opens only when it’s ready (or redirects before showing anything if the data is invalid). First, I will show an example without the Route Resolver, identify its key drawbacks, and then rewrite the example with the Route Resolver.
Step 1: Create Project
Run the following commands
- ng new ResolverDemo
- cd ResolverDemo
- ng serve
Step 2: Add Bootstrap CDN
In src/index.html:
- Add Bootstrap CSS in <head>
- Add Bootstrap JS bundle before </body>
So, please open src/index.html, and copy-paste the following code.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title> ResolverDemo </title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet" />
</head>
<body>
<app-root></app-root>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Step 3: Create Folder Structure
Create folders:
- src/app/models
- src/app/services
- src/app/pages
- src/app/resolvers
Key Points to Remember:
- Pages = screens
- Services = data/business logic
- Resolvers = “load-before-open” logic
- Models = data contracts/types
Step 4: Create Pages
Run these commands:
- ng g c pages/products
- ng g c pages/product-details
- ng g c pages/not-found
Step 5: Create Model
Create a typescript file named product.model.ts within the src/app/models folder, and then copy-paste the following code.
export type Product = {
id: number;
name: string;
price: number;
rating: number;
stock: number;
category: string;
shortDesc: string;
};
Step 6: Create Service (Simulated API)
Create a typescript file named product.service.ts within the src/app/services folder, and then copy-paste the following code.
import { Injectable } from '@angular/core';
import { Product } from '../models/product.model';
// Makes this class injectable as a service
// providedIn: 'root' => one shared instance for the whole app
@Injectable({ providedIn: 'root' })
export class ProductService {
// In-memory product list (acts like a database for this application)
private readonly products: Product[] = [
{ id: 1, name: 'Wireless Mouse', price: 799, rating: 4.5, stock: 28, category: 'Accessories', shortDesc: 'Silent clicks, long battery, ergonomic design.' },
{ id: 2, name: 'Mechanical Keyboard', price: 2999, rating: 4.6, stock: 14, category: 'Accessories', shortDesc: 'Tactile switches, RGB backlight, sturdy build.' },
{ id: 3, name: 'USB-C Hub (7-in-1)', price: 1499, rating: 4.3, stock: 40, category: 'Utility', shortDesc: 'HDMI + USB + SD reader + PD charging.' },
{ id: 4, name: '27-inch IPS Monitor', price: 15999, rating: 4.4, stock: 6, category: 'Displays', shortDesc: 'Sharp colors, thin bezels, eye-care mode.' },
];
// Returns all products immediately (used in listing page)
getAll(): Product[] {
return this.products;
}
// Returns a single product after a delay (simulates API/network call)
// Promise means: "I will return the product later"
async getById(id: number): Promise<Product | null> {
// Simulate network delay (600ms)
await new Promise(resolve => setTimeout(resolve, 600));
// Find product by id
// If not found, return null
return this.products.find(p => p.id === id) ?? null;
}
}
Step 7: Create Products Page (List Page)
Modify Products Component
Open src/app/pages/products/products.ts file, and copy-paste the following code. This component loads all products from the service and exposes them to the template for display. Angular injects ProductService, and we simply call getAll() to populate products for the listing page.
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { ProductService } from '../../services/product.service';
import { Product } from '../../models/product.model';
@Component({
// The HTML tag name we will use if needed: <app-products></app-products>
selector: 'app-products',
standalone: true,
// imports are used in standalone components to enable directives/pipes/components
// RouterLink is needed because we use routerLink in products.html
imports: [RouterLink],
// External template file for this component
templateUrl: './products.html'
})
export class Products {
// This array holds the list of products that will be displayed in the UI
// Initially it's empty, then filled in the constructor
products: Product[] = [];
// Constructor Dependency Injection:
// Angular automatically gives an instance of ProductService here
constructor(private service: ProductService) {
// Get all products from the service (in-memory list)
// We store them in 'products' so the template can show them
this.products = this.service.getAll();
}
}
Modify Products Template
Open src/app/pages/products/products.html file, and copy-paste the following code.
<div class="mb-3">
<h2 class="fw-bold">Products</h2>
<p class="text-muted mb-0">Open any product to see the loader-based approach (without resolver).</p>
</div>
<div class="row g-3">
<!-- Loop through each product in the 'products' array -->
<!-- track p.id helps Angular track items efficiently -->
@for (p of products; track p.id) {
<div class="col-12 col-md-6 col-lg-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<h5 class="fw-semibold mb-1">{{ p.name }}</h5>
<span class="badge text-bg-success">{{ p.rating }} ★</span>
</div>
<p class="text-muted small mb-2">{{ p.shortDesc }}</p>
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="fw-bold">₹{{ p.price }}</div>
<div class="small text-muted">{{ p.category }} • Stock: {{ p.stock }}</div>
</div>
<!-- Navigate to Product Details page using product id -->
<!-- Example: /products/3 -->
<a class="btn btn-outline-dark btn-sm" [routerLink]="['/products', p.id]">
View Details
</a>
</div>
</div>
</div>
</div>
}
</div>
Step 8: Create Product Details Page (Loader in Component)
Modify Product Details Component
Open src/app/pages/product-details/product-details.ts file, and copy-paste the following code. This component reads the product id from the URL, shows a loader, loads the product after a short delay, then hides the loader and displays the product. So, isLoading shows the loader, setTimeout simulates a delay, and detectChanges() ensures UI updates so the loader hides and product data appears.
// Component: Used to create an Angular component using the @Component decorator
// ChangeDetectorRef: Used to manually refresh the UI (we call detectChanges() after Promise completes)
import { Component, ChangeDetectorRef } from '@angular/core';
// ActivatedRoute: Used to read route parameters like /products/:id (to get the "id")
// Router: Used to navigate programmatically (example: redirect to /not-found)
// RouterLink: Needed because the HTML uses routerLink for navigation buttons/links
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
// ProductService: Service class that provides product data (getAll, getById, etc.)
import { ProductService } from '../../services/product.service';
// Product: Model/type used for type safety (product: Product | null)
import { Product } from '../../models/product.model';
@Component({
// Selector name for this component (rarely used directly because routing loads it)
selector: 'app-product-details',
// Standalone component (no NgModule needed)
standalone: true,
// RouterLink is required because HTML contains routerLink (Back button, etc.)
imports: [RouterLink],
// HTML file used as template
templateUrl: './product-details.html'
})
export class ProductDetails {
// Holds the product data to display on the page
// null means data is not loaded yet (or cleared before loading)
product: Product | null = null;
// Controls loader visibility in the HTML
// true => show loader, false => hide loader and show content
isLoading = true;
constructor(
// Used to read route values like /products/:id
private route: ActivatedRoute,
// Used to navigate to other routes programmatically (ex: /not-found)
private router: Router,
// Service that provides product data (now returns Promise with delay)
private service: ProductService,
// Used to force template update after Promise completes (needed in some setups)
private cdr: ChangeDetectorRef
) {
// When component loads, immediately start loading product
this.loadProduct();
}
// Loads product data based on the id coming from URL
// Now this method is async because service.getById(id) returns Promise
private async loadProduct(): Promise<void> {
// 1) Start loader
this.isLoading = true;
// 2) Clear any previously loaded product (safety)
this.product = null;
// 3) Read "id" from URL
// Example: /products/3 => idText = "3"
const idText = this.route.snapshot.paramMap.get('id');
// 4) Convert string id to number
const id = Number(idText);
// 5) Validate the id:
// - idText should not be null
// - id should be a number (not NaN)
// - id should be greater than 0
// If invalid, stop loader and redirect to not-found page
if (!idText || Number.isNaN(id) || id <= 0) {
this.isLoading = false; // stop loader
this.cdr.detectChanges(); // refresh UI
this.router.navigate(['/not-found']); // redirect
return;
}
// 6) Call service method (it waits internally because it has delay)
// We "await" here to get the result after delay
try {
const p = await this.service.getById(id);
// 7) If product is not found, stop loader and redirect
if (!p) {
this.isLoading = false; // stop loader
this.cdr.detectChanges(); // refresh UI
this.router.navigate(['/not-found']); // redirect
return;
}
// 8) If product is found, assign it to show in the HTML
this.product = p;
} finally {
// 9) Always stop loader (whether product found or not)
this.isLoading = false;
// 10) Ensure UI refresh after Promise completes
this.cdr.detectChanges();
}
}
}
Modify Product Details Template
Open src/app/pages/product-details/product-details.html file, and copy-paste the following code.
@if (isLoading) {
<!-- Full Page Overlay Loader -->
<div class="position-fixed top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center bg-light bg-opacity-75"
style="z-index: 2000;">
<div class="bg-white border rounded-3 shadow-sm p-4 text-center" style="min-width: 340px;">
<div class="spinner-border" role="status" aria-label="Loading"></div>
<div class="mt-3 fw-semibold">Loading Product Details...</div>
<div class="text-muted small">Please wait a moment.</div>
</div>
</div>
} @else {
@if (product) {
<!-- Product Details Card -->
<div class="p-4 bg-white border rounded-3 shadow-sm">
<div class="d-flex justify-content-between align-items-start">
<div>
<h2 class="fw-bold mb-1">{{ product.name }}</h2>
<div class="text-muted">{{ product.category }}</div>
</div>
<span class="badge text-bg-success fs-6">{{ product.rating }} ★</span>
</div>
<hr>
<p class="text-muted mb-3">{{ product.shortDesc }}</p>
<div class="d-flex flex-wrap gap-3 align-items-center">
<div class="fs-4 fw-bold">₹{{ product.price }}</div>
<div class="text-muted">
Stock: <span class="fw-semibold">{{ product.stock }}</span>
</div>
</div>
<div class="mt-4">
<a class="btn btn-dark" routerLink="/products">Back</a>
</div>
</div>
}
}
Step 9: Create Not Found Page
Modify Not Found Component
Open src/app/pages/not-found/not-found.ts file, and copy-paste the following code.
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-not-found',
standalone: true,
imports: [RouterLink],
templateUrl: './not-found.html'
})
export class NotFound {}
Modify Not Found Template
Open src/app/pages/not-found/not-found.html file, and copy-paste the following code.
<div class="p-4 bg-white border rounded-3">
<h2 class="fw-bold text-danger">404 - Not Found</h2>
<p class="text-muted">
The page or product you are trying to open does not exist.
</p>
<a class="btn btn-dark" routerLink="/products">Go to Products</a>
</div>
Step 10: Build the App Shell (Navbar + RouterOutlet)
Modify App Component
Open src/app/app.ts file, and copy-paste the following code. This root component loads the router outlet and builds the navbar menu using the links array. App is the main shell component: it enables routing (RouterOutlet) and provides navbar links (links) to our app.html.
// Component is used to create an Angular component using the @Component decorator
import { Component } from '@angular/core';
// RouterOutlet is used to display the page component based on the current route
// RouterLink is used for navigation links like routerLink="/products"
// RouterLinkActive is used to add "active" class when a link is currently selected
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
@Component({
// Selector name for the root component (used in index.html as <app-root></app-root>)
selector: 'app-root',
// standalone: true means this component does not need an NgModule
standalone: true,
// These are required for using router-outlet and routerLink features in app.html
imports: [RouterOutlet, RouterLink, RouterLinkActive],
// External HTML file for the root layout (navbar + router outlet + footer)
templateUrl: './app.html'
})
export class App {
// This array holds navbar menu items
// We use this in app.html with @for to generate the navigation links
links = [
{ path: '/products', label: 'Products' }
];
}
Modify App Template
Open src/app/app.html file, and copy-paste the following code.
<div class="min-vh-100 d-flex flex-column bg-light">
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<!-- Brand (Home = Products) -->
<a class="navbar-brand fw-bold" routerLink="/products">ShopLite</a>
<!-- Mobile Toggle -->
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#mainNav"
aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Nav Links -->
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav ms-auto gap-lg-2">
<!-- Loop through links[] from app.ts and create menu items -->
<!-- track l.path helps Angular track items efficiently -->
@for (l of links; track l.path) {
<li class="nav-item">
<!-- Actual navigation link -->
<!-- [routerLink]="l.path" navigates to that route -->
<!-- routerLinkActive="active" adds active class for current route -->
<!-- exact:true ensures it highlights only exact match -->
<a class="nav-link"
[routerLink]="l.path"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">
{{ l.label }}
</a>
</li>
}
</ul>
</div>
</div>
</nav>
<!-- Page Content -->
<main class="container flex-grow-1 py-4">
<!-- Angular loads the routed component here -->
<!-- Example: Products page, Product Details page, Not Found page -->
<router-outlet></router-outlet>
</main>
<!-- Footer -->
<footer class="bg-dark text-white-50 py-3 mt-auto">
<div class="container small d-flex flex-wrap justify-content-between align-items-center gap-2">
<span>ShopLite • Angular Routing Demo</span>
</div>
</footer>
</div>
Step 11: Routes WITHOUT Resolver
Open src/app/app.routes.ts file, and copy-paste the following code.
import { Routes } from '@angular/router';
import { Products } from './pages/products/products';
import { ProductDetails } from './pages/product-details/product-details';
import { NotFound } from './pages/not-found/not-found';
// This array contains all route definitions for the application
export const routes: Routes = [
// When user opens the root URL: http://localhost:4200/
// redirect to /products (so Products page becomes the home page)
{ path: '', pathMatch: 'full', redirectTo: 'products' },
// When user opens /products, show the Products listing page
{ path: 'products', component: Products },
// WITHOUT Resolver:
// When user opens /products/3 (or any id), show ProductDetails page
// ":id" is a route parameter (dynamic value from URL)
{ path: 'products/:id', component: ProductDetails },
// When user opens /not-found, show NotFound page
{ path: 'not-found', component: NotFound },
// Wildcard route:
// If user types any unknown URL, redirect to /not-found
{ path: '**', redirectTo: 'not-found' }
];
Drawbacks of the Loader-Based Approach (Without Resolver)
When you open /products/3, Angular shows the ProductDetails page immediately, and then the component starts fetching data and shows the loader. So the user experience becomes page opens first → loader appears → data shows later.
Key Points to Remember:
- UI flicker / “half-ready page
-
- The screen appears before the data is available, so it can appear as if the page is loading in pieces.
- Even with a loader, users often feel the page is “not stable” until data arrives.
-
- Component becomes too responsible
-
- The component must handle route ID reading, validation, loading flag, service calls, “not found” errors, and redirects.
- This makes the component longer and harder to maintain as the app grows.
-
- Deep-link navigation is less clean
-
- For invalid URLs like /products/999, the details page still opens first and then redirects.
- With slow networks/devices, that “wrong page moment” becomes more visible.
-
- Loading logic gets duplicated
-
- Every page repeats similar loader code (start loader, stop loader, handle error).
- Later, changing the loader style/behavior requires updating many components.
-
Modify the Same Example WITH Route Resolver
In the loader-based approach, the ProductDetails page opens first, then starts loading data and displays the loader. With a Route Resolver, Angular loads the required product before the route opens, so the ProductDetails page starts with data already available.
- A Resolver runs before the route activates.
-
- If the product exists, the route opens, and the data is ready.
- If the product does not exist → redirect to /not-found before opening the details page.
-
- ProductDetails becomes a display-only component (clean and small).
Step 1: Create Route Resolver
Create a typescript file named product.resolver.ts within the src/app/resolvers folder, and then copy and paste the following code.
import { ResolveFn, ActivatedRouteSnapshot, Router } from '@angular/router';
import { inject } from '@angular/core';
import { ProductService } from '../services/product.service';
import { Product } from '../models/product.model';
// Route Resolver:
// - Before ProductDetails page loads, fetch the product by id from the URL.
// - If the id is invalid OR the product does not exist, redirect to /not-found.
// - If the product exists, return it so the component can read it from route.data.
export const productResolver: ResolveFn<Product | null> = async ( route: ActivatedRouteSnapshot) => {
// Inject required dependencies inside the resolver (Angular functional DI)
const service = inject(ProductService);
const router = inject(Router);
// 1) Read the ":id" route parameter from the URL (e.g., /products/10)
const idText = route.paramMap.get('id');
// 2) Convert the id from string to number
// Example: "10" -> 10, "abc" -> NaN
const id = Number(idText);
// 3) Validate the id BEFORE calling the API/service
// If id is missing, not a number, or <= 0, it is not a valid product id.
// In that case, redirect user to not-found page.
if (!idText || Number.isNaN(id) || id <= 0) {
// Redirect to /not-found so user doesn't stay on an invalid product page
router.navigate(['/not-found']);
// Return null because there is no valid product to resolve.
// (The router won't have real product data to pass to the component.)
return null;
}
// 4) Fetch the product from service based on id
const product = await service.getById(id);
// 5) If service returns null/undefined => product does not exist
// Example: user typed /products/999 but 999 is not in data source.
// Redirect to /not-found to show proper message.
if (!product) {
router.navigate(['/not-found']);
// Return null because we couldn't resolve a valid product.
return null;
}
// 6) Product found => return it.
// Angular will attach it to route.data['product'],
// and then your component can read it safely.
return product;
};
Step 2: Update Routes (Attach Resolver)
Open src/app/app.routes.ts file, and copy and paste the following code.
import { Routes } from '@angular/router';
import { Products } from './pages/products/products';
import { ProductDetails } from './pages/product-details/product-details';
import { NotFound } from './pages/not-found/not-found';
import { productResolver } from './resolvers/product.resolver';
export const routes: Routes = [
// 1) Default Route (Home Page)
// When the user opens the base URL: http://localhost:4200/
// we show the product listing page as the "home" screen.
{ path: '', component: Products },
// 2) Product Listing Route
// URL: /products
// This route displays the Products component which shows all products.
{ path: 'products', component: Products },
// 3) Product Details Route (with Resolver)
// URL Example: /products/3
// Here, "3" is a dynamic route parameter called ":id".
// This route loads ProductDetails component AFTER the resolver finishes its work.
{
path: 'products/:id',
component: ProductDetails,
// "resolve" runs BEFORE the component loads.
// It is used to fetch required data in advance.
//
// What happens:
// 1) User navigates to /products/3
// 2) Angular calls productResolver with id=3
// 3) If product is found => resolver returns product => route activates => ProductDetails loads
// 4) If product is NOT found (or id invalid) => resolver redirects to /not-found (as per resolver logic)
//
// The returned value is stored in route data with the key "product".
// In ProductDetails you can read it using:
// - snapshot (loads once): this.route.snapshot.data['product']
// - better (reactive): this.route.data.subscribe(d => this.product = d['product']);
resolve: { product: productResolver }
},
// 4) Not Found Route
// URL: /not-found
// This is the page shown when:
// - user enters an invalid URL, OR
// - your resolver decides the product id is invalid / product not available.
{ path: 'not-found', component: NotFound },
// 5) Wildcard Route (Catch-All)
// This must always be the LAST route.
// It matches any URL that was not matched above.
// Example: /abc, /wrong-url, /products/xyz-not-handled, etc.
//
// When matched, it redirects to /not-found.
{ path: '**', redirectTo: 'not-found' }
];
Step 3: Simplify Product Details Component (No Loader, No Service Call)
Open src/app/pages/product-details/product-details.ts file and copy-paste the following code. Now ProductDetails does not call the service at all. It simply reads the resolved data.
import { Component } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { Product } from '../../models/product.model';
@Component({
// This is the "HTML tag name" of the component.
selector: 'app-product-details',
// Standalone component means:
standalone: true,
// RouterLink is required because the template uses [routerLink]
// Example: <a routerLink="/products">Back</a>
imports: [RouterLink],
// External template file that contains UI for product details
templateUrl: './product-details.html'
})
export class ProductDetails {
// This will hold the product data that came from the resolver.
// "!" means: we promise Angular that it will be assigned before it is used.
product!: Product;
constructor(private route: ActivatedRoute) {
// The resolver (productResolver) runs BEFORE this component loads.
// If the product is found, Angular stores it in route data using the key: 'product'.
//
// Example:
// resolve: { product: productResolver }
//
// So we can read it here from:
// this.route.snapshot.data['product']
const resolvedProduct = this.route.snapshot.data['product'] as Product | null;
// Safety check:
// If resolver could not find a product, your resolver code redirects to /not-found.
// In that case, resolvedProduct might be null for a moment during navigation.
//
// We simply return and avoid assigning anything.
if (!resolvedProduct) {
// No product available => nothing to display here.
// The user will be taken to the NotFound component by router navigation.
return;
}
// Product is valid and available => store it in the component property
// so the HTML template can display it.
this.product = resolvedProduct;
}
}
Step 4: Simplify ProductDetails HTML (No Loader Needed)
Open src/app/pages/product-details/product-details.html file and copy-paste the following code.
<div class="p-4 bg-white border rounded-3 shadow-sm">
<div class="d-flex justify-content-between align-items-start">
<div>
<h2 class="fw-bold mb-1">{{ product.name }}</h2>
<div class="text-muted">{{ product.category }}</div>
</div>
<span class="badge text-bg-success fs-6">{{ product.rating }} ★</span>
</div>
<hr>
<p class="text-muted mb-3">{{ product.shortDesc }}</p>
<div class="d-flex flex-wrap gap-3 align-items-center">
<div class="fs-4 fw-bold">₹{{ product.price }}</div>
<div class="text-muted">
Stock: <span class="fw-semibold">{{ product.stock }}</span>
</div>
</div>
<div class="mt-4">
<a class="btn btn-dark" routerLink="/products">Back</a>
</div>
</div>
With a Route Resolver, you shift data loading from the component to the routing layer, so ProductDetails opens only when the product is already available.
Conclusion
Route Resolvers help Angular applications load pages more predictably and professionally by ensuring that required data is available before a route is activated. Instead of opening a page and loading data in the component, resolvers shift this responsibility to the routing layer.
As a result, components become cleaner, navigation feels smoother, invalid URLs are handled earlier, and users see pages only when they are fully ready. In real-world applications, especially detail pages and dashboards, Route Resolvers greatly improve both user experience and code maintainability.
