Table of Contents
- What is Angular Routing?
- Setting Up Angular Routing
- Basic Routing Concepts
- Route Parameters: Dynamic Routes
- Child Routes: Nested Navigation
- Route Guards: Protecting Routes
- Lazy Loading: Optimizing Performance
- Advanced Routing Techniques
- Best Practices for Angular Routing
- References
1. What is Angular Routing?
Angular Routing is a module (RouterModule) that enables navigation between different components in an Angular application. It maps URLs to components, synchronizes the browser’s URL with the application state, and supports features like dynamic routes, nested navigation, and route protection.
Key benefits of Angular routing:
- Single-Page Application (SPA) Behavior: No full-page reloads—only the routed component updates.
- URL Management: Users can bookmark or share URLs, and the browser’s back/forward buttons work as expected.
- Modularity: Routes can be organized into feature modules, improving maintainability.
- Performance: Lazy loading reduces initial bundle size by loading non-critical code on demand.
2. Setting Up Angular Routing
To use routing in Angular, you’ll need to set up the RouterModule. Here’s how:
Option 1: Add Routing During Project Creation
When creating a new Angular app with the CLI, include the --routing flag to auto-generate a routing module:
ng new my-routing-app --routing
Option 2: Add Routing Manually
If you already have an app, create a routing module manually:
-
Generate a routing module:
ng generate module app-routing --flat --module=app--flat: Puts the file insrc/appinstead of a subfolder.--module=app: Registers the routing module with the rootAppModule.
-
The generated
app-routing.module.tswill look like this:import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; const routes: Routes = []; // Define routes here @NgModule({ imports: [RouterModule.forRoot(routes)], // Configure root routes exports: [RouterModule] // Make RouterModule available to the app }) export class AppRoutingModule { } -
Import
AppRoutingModuleintoAppModule(auto-done if using--module=app).
3. Basic Routing Concepts
3.1 Configuring Routes
Routes are defined as an array of Route objects in the routing module. Each Route specifies a path (URL segment) and a component to render when that path is matched.
Example: Basic Route Configuration
Update app-routing.module.ts to define routes for Home, About, and a 404 page:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { NotFoundComponent } from './not-found/not-found.component';
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' }, // Default route
{ path: '**', component: NotFoundComponent } // Wildcard route (404)
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
path: '': Matches the root URL (http://localhost:4200). UseredirectToto send users to/home.pathMatch: 'full': Ensures the entire URL is matched (avoids partial matches).path: '**': Wildcard route—matches any URL not defined above (use for 404 pages).
3.2 Router Outlet
The <router-outlet> directive marks the location where the routed component will be rendered. Add it to your root component template (app.component.html):
<!-- app.component.html -->
<nav>
<!-- Navigation links will go here -->
</nav>
<router-outlet></router-outlet> <!-- Routed components render here -->
3.3 Navigation Links (RouterLink)
Use the routerLink directive to create navigation links. It replaces <a href> tags and prevents full-page reloads.
Basic Links
<!-- app.component.html -->
<nav>
<a routerLink="/home" routerLinkActive="active">Home</a>
<a routerLink="/about" routerLinkActive="active">About</a>
</nav>
routerLink="/home": Binds to the/homepath.routerLinkActive="active": Adds theactiveCSS class to the link when its route is active (style it instyles.css).
Dynamic Links
For dynamic paths (e.g., with variables), use property binding:
<!-- Navigate to /user/123 -->
<a [routerLink]="['/user', userId]">View User</a>
4. Route Parameters: Dynamic Routes
Route parameters let you define dynamic URL segments (e.g., /user/123, where 123 is a user ID).
Step 1: Define a Route with a Parameter
Add a route with a parameter placeholder (:paramName) in app-routing.module.ts:
const routes: Routes = [
// ... other routes
{ path: 'user/:id', component: UserComponent } // :id is the parameter
];
Step 2: Access Parameters in the Component
Use ActivatedRoute to access route parameters. Inject ActivatedRoute into the component constructor:
// user.component.ts
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user',
template: `<h2>User ID: {{ userId }}</h2>`
})
export class UserComponent {
userId: string;
constructor(private route: ActivatedRoute) {
// Snapshot: Get parameters once (use for static data)
this.userId = this.route.snapshot.paramMap.get('id');
// Observable: Subscribe to parameter changes (use for dynamic updates)
this.route.paramMap.subscribe(params => {
this.userId = params.get('id');
});
}
}
- Snapshot: Use
route.snapshotfor one-time access (e.g., initial load). - Observable: Use
route.paramMap.subscribe()if the parameter might change (e.g., navigating from/user/123to/user/456without reloading the component).
5. Child Routes: Nested Navigation
Child routes enable nested UI layouts (e.g., a dashboard with tabs like /dashboard/stats and /dashboard/settings).
Step 1: Configure Child Routes
Define child routes using the children property in a parent route:
// app-routing.module.ts
const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
children: [
{ path: 'stats', component: StatsComponent }, // /dashboard/stats
{ path: 'settings', component: SettingsComponent }, // /dashboard/settings
{ path: '', redirectTo: 'stats', pathMatch: 'full' } // Default child route
]
}
];
Step 2: Add a Child Router Outlet
In the parent component (DashboardComponent), add a <router-outlet> to render child components:
<!-- dashboard.component.html -->
<h2>Dashboard</h2>
<nav>
<a routerLink="stats">Stats</a> <!-- Relative path (to /dashboard) -->
<a routerLink="settings">Settings</a>
</nav>
<router-outlet></router-outlet> <!-- Child components render here -->
6. Route Guards: Protecting Routes
Route guards control access to routes (e.g., restrict to authenticated users) or prevent accidental navigation (e.g., unsaved form changes). They implement interfaces like CanActivate, CanActivateChild, or CanDeactivate.
6.1 canActivate: Control Access to Routes
Use canActivate to restrict access to a route (e.g., only logged-in users).
Step 1: Create an Auth Guard
Generate a guard with the CLI:
ng generate guard auth
Select CanActivate when prompted. Update the guard to check authentication:
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; // Simplified check
if (!isLoggedIn) {
this.router.navigate(['/login']); // Redirect to login if not authenticated
return false;
}
return true;
}
}
Step 2: Apply the Guard to a Route
Add the guard to the route in app-routing.module.ts:
const routes: Routes = [
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard] // Only authenticated users can access
}
];
6.2 canActivateChild: Protect Child Routes
canActivateChild works like canActivate but applies to all child routes of a parent.
// auth.guard.ts (add CanActivateChild)
import { CanActivateChild } from '@angular/router';
export class AuthGuard implements CanActivate, CanActivateChild {
// ... canActivate logic
canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
return this.canActivate(childRoute, state); // Reuse canActivate logic
}
}
Apply to the parent route:
{
path: 'admin',
component: AdminComponent,
canActivateChild: [AuthGuard], // Protects all child routes
children: [/* ... */]
}
6.3 canDeactivate: Prevent Unsaved Changes
canDeactivate prevents navigation away from a component if there are unsaved changes (e.g., a form).
Step 1: Create a CanDeactivate Guard
Generate a guard and select CanDeactivate:
ng generate guard unsaved-changes
Step 2: Define a Component Interface
Create an interface for components that use the guard:
// unsaved-changes.guard.ts
import { CanDeactivate } from '@angular/router';
export interface CanComponentDeactivate {
canDeactivate: () => boolean | Promise<boolean>;
}
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate): boolean | Promise<boolean> {
return component.canDeactivate();
}
}
Step 3: Implement the Interface in the Component
// edit-profile.component.ts
import { Component } from '@angular/core';
import { CanComponentDeactivate } from './unsaved-changes.guard';
@Component({ /* ... */ })
export class EditProfileComponent implements CanComponentDeactivate {
hasUnsavedChanges = false;
canDeactivate(): boolean | Promise<boolean> {
if (this.hasUnsavedChanges) {
return confirm('You have unsaved changes. Do you want to leave?');
}
return true;
}
}
Step 4: Apply the Guard to the Route
{
path: 'edit-profile',
component: EditProfileComponent,
canDeactivate: [UnsavedChangesGuard]
}
7. Lazy Loading: Optimizing Performance
Lazy loading loads feature modules only when the user navigates to their routes, reducing the initial bundle size and improving load times.
Step 1: Create a Feature Module with Routing
Generate a feature module (e.g., ProductsModule) with its own routing module:
ng generate module products --routing
Step 2: Define Routes in the Feature Module
Update products-routing.module.ts with the feature’s routes:
// products-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductsListComponent } from './products-list/products-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
const routes: Routes = [
{ path: '', component: ProductsListComponent }, // /products
{ path: ':id', component: ProductDetailComponent } // /products/123
];
@NgModule({
imports: [RouterModule.forChild(routes)], // Use forChild() for feature routes
exports: [RouterModule]
})
export class ProductsRoutingModule { }
Step 3: Lazy Load the Module in the Root Routing Module
In app-routing.module.ts, use loadChildren to lazy load the feature module:
const routes: Routes = [
// ... other routes
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
}
];
Now, ProductsModule (and its components) will only load when the user navigates to /products.
8. Advanced Routing Techniques
8.1 Route Resolvers: Fetch Data Before Navigation
Resolvers fetch data before a component loads, ensuring the component has data when it initializes.
Step 1: Create a Resolver
Generate a resolver:
ng generate resolver user-resolver
Step 2: Implement the Resolver
// user-resolver.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';
@Injectable({ providedIn: 'root' })
export class UserResolver implements Resolve<User> {
constructor(private userService: UserService) {}
resolve(route: ActivatedRouteSnapshot): Promise<User> {
const userId = route.paramMap.get('id');
return this.userService.getUserById(userId); // Fetch data from API
}
}
Step 3: Attach the Resolver to a Route
// app-routing.module.ts
const routes: Routes = [
{
path: 'user/:id',
component: UserComponent,
resolve: { user: UserResolver } // Resolver key: 'user'
}
];
Step 4: Access Resolved Data in the Component
// user.component.ts
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { User } from './user.model';
@Component({ /* ... */ })
export class UserComponent {
user: User;
constructor(private route: ActivatedRoute) {
this.user = this.route.snapshot.data.user; // Get resolved data
}
}
8.2 Query Parameters: Passing Optional Data
Query parameters (e.g., ?page=1&sort=asc) are used for optional data like pagination or search filters.
Add Query Parameters
<!-- Navigate with query params -->
<a [routerLink]="['/products']" [queryParams]="{ page: 1, sort: 'name' }">Products</a>
Access Query Parameters
// products.component.ts
import { ActivatedRoute } from '@angular/router';
constructor(private route: ActivatedRoute) {
// Snapshot (one-time access)
const page = this.route.snapshot.queryParamMap.get('page');
// Observable (updates on navigation)
this.route.queryParamMap.subscribe(params => {
this.page = params.get('page');
this.sort = params.get('sort');
});
}
8.3 Route Transitions: Animating Navigation
Use Angular’s animation system to animate route transitions (e.g., fade, slide).
Step 1: Define an Animation
// route-animations.ts
import { trigger, transition, style, animate } from '@angular/animations';
export const routeTransition = trigger('routeTransition', [
transition('* <=> *', [ // Apply to all route changes
style({ position: 'relative' }),
animate('0.3s ease', style({ opacity: 1 }))
])
]);
Step 2: Apply the Animation to Router Outlet
// app.component.ts
import { Component } from '@angular/core';
import { routeTransition } from './route-animations';
@Component({
selector: 'app-root',
template: `
<nav>...</nav>
<router-outlet [@routeTransition]></router-outlet>
`,
animations: [routeTransition]
})
export class AppComponent { }
9. Best Practices for Angular Routing
- Modularize Routes: Organize routes into feature modules (e.g.,
ProductsRoutingModule). - Use Lazy Loading: Reduce initial bundle size by lazy loading non-critical modules.
- Handle 404s: Always include a wildcard route (
**) for 404 pages. - Prefer Observables Over Snapshots: Use
route.paramMap.subscribe()for dynamic parameter updates. - Avoid Deep Nesting: Limit child routes to 2–3 levels for readability.
- Test Route Guards: Ensure guards correctly block/unblock routes (e.g., authenticated vs. unauthenticated users).
10. References
- Angular Router Official Documentation
- Route Guards
- Lazy Loading Modules
- Route Resolvers
- Angular Animations
By mastering Angular routing, you’ll build robust, performant, and user-friendly SPAs. Start with the basics, experiment with dynamic routes and guards, and optimize with lazy loading—your users (and your bundle size) will thank you! 🚀