Skip to main content

Modern Routing with Standalone Components

Overview

Angular 15+ introduces modern routing with provideRouter, functional guards, and lazy loading via loadComponent.

Basic Setup

main.ts Configuration

import { bootstrapApplication } from "@angular/platform-browser";
import { provideRouter } from "@angular/router";
import { AppComponent } from "./app/app.component";
import { routes } from "./app/app.routes";

bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)],
});

Route Configuration

// app.routes.ts
import { Routes } from "@angular/router";
import { HomeComponent } from "./home/home.component";
import { AboutComponent } from "./about/about.component";

export const routes: Routes = [
{ path: "", component: HomeComponent },
{ path: "about", component: AboutComponent },
{ path: "**", redirectTo: "" },
];

Lazy Loading with loadComponent

Feature Route Lazy Loading

// app.routes.ts
import { Routes } from "@angular/router";

export const routes: Routes = [
{
path: "dashboard",
loadComponent: () => import("./dashboard/dashboard.component").then((m) => m.DashboardComponent),
},
{
path: "profile",
loadComponent: () => import("./profile/profile.component").then((m) => m.ProfileComponent),
},
];

Lazy Loading Child Routes

// app.routes.ts
export const routes: Routes = [
{
path: "admin",
loadChildren: () => import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
},
];

// admin/admin.routes.ts
import { Routes } from "@angular/router";

export const ADMIN_ROUTES: Routes = [
{
path: "",
loadComponent: () => import("./admin-dashboard.component").then((m) => m.AdminDashboardComponent),
},
{
path: "users",
loadComponent: () => import("./users/user-list.component").then((m) => m.UserListComponent),
},
];

Functional Guards

CanActivate Guard

// guards/auth.guard.ts
import { inject } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from "../services/auth.service";

export const authGuard = () => {
const authService = inject(AuthService);
const router = inject(Router);

if (authService.isAuthenticated()) {
return true;
}

return router.createUrlTree(["/login"]);
};

Usage in Routes

import { authGuard } from "./guards/auth.guard";

export const routes: Routes = [
{
path: "dashboard",
loadComponent: () => import("./dashboard/dashboard.component").then((m) => m.DashboardComponent),
canActivate: [authGuard],
},
];

CanDeactivate Guard

// guards/unsaved-changes.guard.ts
import { CanDeactivateFn } from "@angular/router";
Change Detection Deep Dive
Default vs OnPush, signals integration.
Zoneless change detection, markDirty(), explicit triggers.
Advanced DI e<boolean>;
}

export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> = (component) => {
return component.canDeactivate ? component.canDeactivate() : true;
};

Functional Resolvers

Basic Resolver

// resolvers/user.resolver.ts
import { inject } from "@angular/core";
import { ResolveFn } from "@angular/router";
import { UserService } from "../services/user.service";
import { User } from "../models/user.model";

export const userResolver: ResolveFn<User> = (route, state) => {
const userService = inject(UserService);
const userId = route.paramMap.get("id")!;
return userService.getUserById(userId);
};

Using Resolver in Routes

import { userResolver } from "./resolvers/user.resolver";

export const routes: Routes = [
{
path: "user/:id",
loadComponent: () => import("./user-detail/user-detail.component").then((m) => m.UserDetailComponent),
resolve: { user: userResolver },
},
];

Accessing Resolved Data

import { Component, inject } from "@angular/core";
import { ActivatedRoute } from "@angular/router";

@Component({
selector: "app-user-detail",
standalone: true,
template: `<h1>{{ user.name }}</h1>`,
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
user = this.route.snapshot.data["user"];
}

Preloading Strategies

Enable Preloading

import { bootstrapApplication } from "@angular/platform-browser";
import { provideRouter, PreloadAllModules } from "@angular/router";
import { routes } from "./app/app.routes";

bootstrapApplication(AppComponent, {
providers: [provideRouter(routes, withPreloading(PreloadAllModules))],
});

Custom Preloading Strategy

// strategies/selective-preload.strategy.ts
import { Injectable } from "@angular/core";
import { PreloadingStrategy, Route } from "@angular/router";
import { Observable, of } from "rxjs";

@Injectable({ providedIn: "root" })
export class SelectivePreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
return route.data?.["preload"] ? load() : of(null);
}
}
// Usage
export const routes: Routes = [
{
path: "dashboard",
data: { preload: true }, // This will be preloaded
loadComponent: () => import("./dashboard.component").then((m) => m.DashboardComponent),
},
];

Route Parameters and Data

Type-Safe Route Data

// Using route inputs (Angular 16+)
import { Component, Input } from "@angular/core";

@Component({
selector: "app-product",
standalone: true,
template: `<h1>Product {{ id }}</h1>`,
})
export class ProductComponent {
@Input() id!: string; // Automatically populated from route param
}

// Enable in route config
export const routes: Routes = [
{
path: "product/:id",
component: ProductComponent,
// Angular 16+ feature
},
];

Complete Example

// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { provideRouter, withPreloading, PreloadAllModules } from "@angular/router";
import { provideHttpClient } from "@angular/common/http";
import { AppComponent } from "./app/app.component";
import { routes } from "./app/app.routes";

bootstrapApplication(AppComponent, {
providers: [provideRouter(routes, withPreloading(PreloadAllModules)), provideHttpClient()],
}).catch((err) => console.error(err));

// app.routes.ts
import { Routes } from "@angular/router";
import { authGuard } from "./guards/auth.guard";
import { userResolver } from "./resolvers/user.resolver";

export const routes: Routes = [
{
path: "",
loadComponent: () => import("./home/home.component").then((m) => m.HomeComponent),
},
{
path: "dashboard",
loadComponent: () => import("./dashboard/dashboard.component").then((m) => m.DashboardComponent),
canActivate: [authGuard],
},
{
path: "user/:id",
loadComponent: () => import("./user/user-detail.component").then((m) => m.UserDetailComponent),
resolve: { user: userResolver },
},
{
path: "admin",
loadChildren: () => import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
canActivate: [authGuard],
},
{ path: "**", redirectTo: "" },
];

Interview Questions

Q: What's the difference between bootstrapModule and bootstrapApplication? A: bootstrapModule requires an NgModule, while bootstrapApplication works with standalone components and uses provider functions like provideRouter.

Q: How do functional guards differ from class-based guards? A: Functional guards are simpler functions that use inject() for DI, eliminating boilerplate and making testing easier.

Q: What are the benefits of loadComponent over loadChildren with modules? A: Direct component loading, smaller bundles, no NgModule overhead, and clearer lazy loading boundaries.