const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'users', component: UsersComponent },
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
// In the template, use router-outlet for rendering
// <router-outlet></router-outlet>
Use forRoot() in the app module and forChild() in feature modules. The <router-outlet> directive marks where routed components are displayed.
Why it matters: Tests knowledge of routing module setup. Shows foundational routing understanding.
Real applications: Any multi-page Angular application, single-page applications with navigation, dashboard applications, e-commerce sites.
Common mistakes: Developers forget forRoot() vs forChild() distinction. They place router-outlet in wrong location. They don't handle 404 routes.
// Route definition
{ path: 'users/:id', component: UserDetailComponent }
// Accessing params in the component
@Component({ template: '<h2>User {{ userId }}</h2>' })
export class UserDetailComponent implements OnInit {
userId!: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Snapshot (non-reactive)
this.userId = this.route.snapshot.paramMap.get('id')!;
// Observable (reactive - responds to param changes)
this.route.paramMap.subscribe(params => {
this.userId = params.get('id')!;
});
}
}
Use the observable approach when the same component may be reused with different parameters. The snapshot is sufficient when the component is always destroyed and recreated on each navigation.
Why it matters: Tests understanding of route parameter binding. Shows you know when to use reactive patterns.
Real applications: User profile pages with different IDs, product details from route parameters, edit screens with dynamic data.
Common mistakes: Developers use only snapshot and miss parameter changes. They don't unsubscribe from observable, causing memory leaks. They forget non-null assertion (!).
// Navigate with query params
<a [routerLink]="['/products']" [queryParams]="{ sort: 'price', page: 1 }">
Products
</a>
// Programmatic navigation
this.router.navigate(['/products'], {
queryParams: { sort: 'price', page: 1 },
queryParamsHandling: 'merge' // or 'preserve'
});
// Reading query params
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.queryParamMap.subscribe(params => {
const sort = params.get('sort');
const page = Number(params.get('page'));
});
}
queryParamsHandling: 'merge' merges new params with existing ones. 'preserve' keeps the current query params unchanged.
Why it matters: Tests knowledge of URL query parameter patterns. Shows you understand filtering and pagination.
Real applications: Pagination (page, size), sorting (sort, order), filtering (category, price range), search results, UI state preservation.
Common mistakes: Developers hardcode parameters in URLs instead of using queryParams. They don't handle queryParamsHandling properly. They don't decode special characters in query strings.
import { Router } from '@angular/router';
export class HeaderComponent {
constructor(private router: Router) {}
goToUser(id: number) {
// Navigate with route array
this.router.navigate(['/users', id]);
}
goToSearch(term: string) {
// Navigate with query params
this.router.navigate(['/search'], { queryParams: { q: term } });
}
goToAbsolute() {
// Navigate by full URL string
this.router.navigateByUrl('/about');
}
}
navigate() accepts an array of route segments and extras. navigateByUrl() accepts a full URL string. Both return a Promise that resolves to true if navigation succeeds.
Why it matters: Tests knowledge of programmatic navigation patterns. Shows you understand imperative routing.
Real applications: Click handlers triggering navigation, conditional routing after form submission, multi-step wizard flows, permission-based redirects.
Common mistakes: Developers don't check if navigation succeeded (the returned Promise). They mix navigate and navigateByUrl inappropriately. They forget relative path navigation.
// App routing module
const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'admin',
loadChildren: () => import('./admin/admin.module')
.then(m => m.AdminModule)
},
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component')
.then(c => c.DashboardComponent) // standalone component
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)]
})
export class AppRoutingModule {}
Use loadChildren for lazy-loading modules and loadComponent for lazy-loading standalone components. The module is fetched only when the user navigates to that route.
Why it matters: Tests knowledge of performance optimization through lazy loading. Shows you understand bundle splitting.
Real applications: Admin panels loaded only for admins, feature modules not used by all users, reducingInitial page load time for large applications.
Common mistakes: Developers don't use lazy loading and ship entire app in one bundle. They mix static and lazy-loaded routes incorrectly. They don't understand that lazy-loaded modules need guards.
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
children: [
{ path: '', component: AdminDashboardComponent },
{ path: 'users', component: AdminUsersComponent },
{ path: 'settings', component: AdminSettingsComponent }
]
}
];
// admin.component.html
// <nav>
// <a routerLink="users">Users</a>
// <a routerLink="settings">Settings</a>
// </nav>
// <router-outlet></router-outlet>
The parent component must contain its own <router-outlet> to display child route components. Child routes inherit the parent's path prefix, so 'users' becomes /admin/users.
Why it matters: Tests knowledge of nested routing architectures. Shows you understand layout composition.
Real applications: Admin dashboards with nested modules, multi-level navigation structures, dashboard sections with sub-sections, hierarchical content organization.
Common mistakes: Developers forget the parent component needs its own router-outlet. They don't use relative routing in child routes. They hardcode full paths instead of using relative links.
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'contact', component: ContactComponent },
// Wildcard route MUST be last
{ path: '**', component: PageNotFoundComponent }
];
@Component({
selector: 'app-not-found',
template: '<h1>404 - Page Not Found</h1><a routerLink="/">Go Home</a>'
})
export class PageNotFoundComponent {}
The wildcard route must be the last route in the configuration because Angular matches routes in first-match order. Placing ** earlier would intercept all navigation and prevent other routes from matching.
Why it matters: Tests knowledge of route matching and error handling. Shows you understand pattern matching order.
Real applications: 404 error pages, fallback pages for unhandled routes, catch-all handlers for undefined navigation.
Common mistakes: Developers place ** before specific routes and break navigation. They don't provide a 404 page. They don't handle the wildcard case gracefully.
const routes: Routes = [
// Redirect empty path to /home
{ path: '', redirectTo: '/home', pathMatch: 'full' },
// Redirect old URL to new URL
{ path: 'legacy-page', redirectTo: '/new-page', pathMatch: 'full' },
// Redirect with prefix matching
{ path: 'old', redirectTo: '/new', pathMatch: 'prefix' },
{ path: 'home', component: HomeComponent },
{ path: 'new-page', component: NewPageComponent },
{ path: '**', redirectTo: '/home' }
];
pathMatch: 'full' requires the entire URL to match the path. pathMatch: 'prefix' matches if the URL starts with the path. Always use 'full' for empty path redirects to avoid matching everything.
Why it matters: Tests knowledge of route redirection patterns. Shows you understand URL matching strategies.
Real applications: Redirect empty path to default page, migrate URLs from old structure to new, create vanity URLs, handle backwards compatibility.
Common mistakes: Developers use 'prefix' for empty path redirect, redirecting everything. They don't put redirects before specific routes. They don't understand pathMatch semantics.
export class ProductComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Route parameters
this.route.paramMap.subscribe(p => console.log(p.get('id')));
// Query parameters
this.route.queryParamMap.subscribe(q => console.log(q.get('sort')));
// Route data (static)
this.route.data.subscribe(d => console.log(d['title']));
// URL segments
this.route.url.subscribe(segments => console.log(segments));
// Snapshot (non-reactive)
const id = this.route.snapshot.paramMap.get('id');
}
}
Key properties: paramMap, queryParamMap, data, url, fragment, outlet, parent, children. Each is available as both an Observable and a snapshot.
Why it matters: Tests knowledge of route metadata access. Shows you understand how to read routing context in components.
Real applications: Read ID from URL to fetch data, get query filters for lists, access breadcrumb titles from route data, detect current route information.
Common mistakes: Developers only use snapshot and miss dynamic updates. They don't unsubscribe from observables, causing memory leaks. They try to access parent/children routes incorrectly.
import { Router, NavigationStart, NavigationEnd, NavigationError } from '@angular/router';
export class AppComponent {
loading = false;
constructor(private router: Router) {
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.loading = true;
}
if (event instanceof NavigationEnd) {
this.loading = false;
console.log('Navigated to:', event.urlAfterRedirects);
}
if (event instanceof NavigationError) {
this.loading = false;
console.error('Navigation failed:', event.error);
}
});
}
}
Key events in order: NavigationStart → RouteConfigLoadStart (lazy) → RoutesRecognized → GuardsCheckStart → GuardsCheckEnd → ResolveStart → ResolveEnd → NavigationEnd.
Why it matters: Tests knowledge of navigation lifecycle and timing. Shows you understand when to apply logic during navigation.
Real applications: Show/hide loading spinner, track analytics events, log navigation, reset UI state, handle errors gracefully.
Common mistakes: Developers don't unsubscribe from router.events, causing memory leaks. They check wrong event types. They don't handle all edge cases (NavigationCancel, NavigationError).
<nav>
<a routerLink="/home" routerLinkActive="active">Home</a>
<a [routerLink]="['/users', userId]" routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">Profile</a>
<a routerLink="/settings" routerLinkActive="active highlight">Settings</a>
</nav>
routerLinkActive can accept multiple CSS class names separated by spaces. Setting exact: true ensures the class is only applied when the full URL matches exactly.
Why it matters: Tests knowledge of template routing directives. Shows you understand declarative navigation.
Real applications: Highlight active menu items, style current navigation links, implement breadcrumb active states, show active tabs.
Common mistakes: Developers forget routerLinkActive completely. They don't use exact matching, causing wrong items to highlight. They pass wrong CSS class names.
// App module: forRoot (only once in the entire app)
@NgModule({
imports: [RouterModule.forRoot(appRoutes)]
})
export class AppModule { }
// Feature module: forChild (can be used in multiple modules)
@NgModule({
imports: [RouterModule.forChild(featureRoutes)]
})
export class FeatureModule { }
Always use forRoot() only in the AppModule and forChild() in all feature modules. This prevents duplicate Router instances and routing conflicts.
Why it matters: Tests understanding of Angular module architecture. Shows you know singleton vs multi-instance services.
Real applications: Organizing large apps into feature modules, lazy-loaded feature routes, keeping routing centralized yet modular.
Common mistakes: Developers use forRoot() in feature modules, creating multiple Router instances. They don't use forChild() consistently. They don't understand the distinction.
<!-- Template with multiple outlets -->
<router-outlet></router-outlet>
<router-outlet name="sidebar"></router-outlet>
<!-- Route configuration -->
{ path: 'help', component: HelpComponent, outlet: 'sidebar' }
<!-- Navigation -->
<a [routerLink]="[{ outlets: { sidebar: ['help'] } }]">Show Help</a>
The URL shows named outlets in parentheses like /home(sidebar:help). You can clear a named outlet by navigating to null for that outlet.
Why it matters: Tests knowledge of advanced layout patterns. Shows you understand complex routing scenarios.
Real applications: Sidebars with independent content, popup panels, chat widgets alongside main content, multi-panel layouts.
Common mistakes: Developers don't know about named outlets. They try to force multiple components into a single outlet. They don't understand outlet syntax in URLs.
// Route configuration with static data
{ path: 'admin', component: AdminComponent,
data: { title: 'Admin Panel', roles: ['admin'], breadcrumb: 'Admin' }
}
// Accessing data in component
export class AdminComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit() {
const title = this.route.snapshot.data['title'];
const roles = this.route.snapshot.data['roles'];
}
}
Route data is ideal for static metadata that does not change. For dynamic data fetched from an API, use a resolver instead.
Why it matters: Tests knowledge of route metadata patterns. Shows you can decouple data from code.
Real applications: Setting page titles dynamically, controlling permissions, passing breadcrumb labels, feature toggles, documentation links.
Common mistakes: Developers hardcode metadata in components instead of route data. They don't use route data for permissions. They forget data is type-unsafe without careful validation.
// Preload all lazy modules in background
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})
// Custom preloading: only preload routes with data.preload = true
export class SelectivePreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>) {
return route.data?.['preload'] ? load() : of(null);
}
}
PreloadAllModules is a good default for small apps. For larger apps, a custom strategy gives better control over which modules are preloaded based on user behavior.
Why it matters: Tests knowledge of performance optimization with lazy loading. Shows you understand preloading strategies.
Real applications: Improve perceived performance by preloading frequently-used modules, reduce wait time for navigation to common features, optimize user experience.
Common mistakes: Developers use PreloadAllModules on large apps, negating lazy loading benefits. They don't create custom strategies. They preload too aggressively causing unnecessary bandwidth.