The Angular 18 Archetype uses the Angular Router with functional guards and standalone components. This guide covers route configuration, protection with guards, and lazy loading strategies.
Routes configuration
Routes are defined in app.routes.ts:
// src/app/app.routes.ts
import { Routes } from '@angular/router' ;
export const routes : Routes = [];
The archetype starts with an empty routes array. This provides a clean slate for building your application’s routing structure.
Adding routes
Basic route
import { Routes } from '@angular/router' ;
import { HomeComponent } from './pages/home.component' ;
export const routes : Routes = [
{
path: '' ,
component: HomeComponent ,
},
{
path: 'about' ,
component: AboutComponent ,
},
];
Route with children
export const routes : Routes = [
{
path: 'dashboard' ,
component: DashboardLayoutComponent ,
children: [
{ path: '' , component: DashboardHomeComponent },
{ path: 'profile' , component: ProfileComponent },
{ path: 'settings' , component: SettingsComponent },
],
},
];
Redirect route
export const routes : Routes = [
{
path: '' ,
redirectTo: '/home' ,
pathMatch: 'full' ,
},
{
path: 'home' ,
component: HomeComponent ,
},
];
Wildcard route (404)
export const routes : Routes = [
// ... other routes
{
path: '**' ,
component: NotFoundComponent ,
},
];
Always place wildcard routes last, as routes are matched in order from top to bottom.
Lazy loading
Lazy loading improves initial load time by loading routes on-demand:
Lazy load a component
export const routes : Routes = [
{
path: 'dashboard' ,
loadComponent : () => import ( './pages/dashboard/dashboard.component' )
. then ( m => m . DashboardComponent ),
},
];
Lazy load child routes
export const routes : Routes = [
{
path: 'admin' ,
loadChildren : () => import ( './admin/admin.routes' )
. then ( m => m . ADMIN_ROUTES ),
},
];
// src/app/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.component' )
. then ( m => m . UsersComponent ),
},
];
Preloading strategy
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core' ;
import { provideRouter , withPreloading , PreloadAllModules } from '@angular/router' ;
import { routes } from './app.routes' ;
export const appConfig : ApplicationConfig = {
providers: [
provideRouter (
routes ,
withPreloading ( PreloadAllModules ) // Preload all lazy routes after initial load
),
]
};
Using the auth guard
The archetype includes an authGuard to protect routes that require authentication:
// src/app/core/providers/auth.guard.ts
import { inject } from '@angular/core' ;
import { CanActivateFn , Router } from '@angular/router' ;
import { environment } from '@env/environment' ;
import { AuthStore } from '@services/state/auth.store' ;
export const authGuard : CanActivateFn = () => {
if ( environment . securityOpen ) return true ;
const authStore = inject ( AuthStore );
if ( authStore . isAuthenticated ()) return true ;
const router = inject ( Router );
return router . createUrlTree ([ '/auth' , 'login' ]);
};
Protecting routes
Single route
Multiple routes
Protect all child routes
export const routes : Routes = [
{
path: 'profile' ,
component: ProfileComponent ,
canActivate: [ authGuard ],
},
];
How authGuard works
Check environment
If environment.securityOpen is true, allow access (useful for development)
Check authentication
Read isAuthenticated() signal from AuthStore
Allow or redirect
If authenticated: return true (allow navigation)
If not authenticated: return UrlTree to /auth/login (redirect)
Creating custom guards
Role-based guard
import { inject } from '@angular/core' ;
import { CanActivateFn , Router } from '@angular/router' ;
import { AuthStore } from '@services/state/auth.store' ;
export const adminGuard : CanActivateFn = () => {
const authStore = inject ( AuthStore );
const router = inject ( Router );
if ( ! authStore . isAuthenticated ()) {
return router . createUrlTree ([ '/auth' , 'login' ]);
}
const user = authStore . user ();
if ( user . role === 'admin' ) {
return true ;
}
return router . createUrlTree ([ '/unauthorized' ]);
};
Unsaved changes guard
import { CanDeactivateFn } from '@angular/router' ;
export interface CanComponentDeactivate {
canDeactivate : () => boolean ;
}
export const unsavedChangesGuard : CanDeactivateFn < CanComponentDeactivate > = ( component ) => {
if ( component . canDeactivate ()) {
return true ;
}
return confirm ( 'You have unsaved changes. Do you really want to leave?' );
};
Usage:
export class EditFormComponent implements CanComponentDeactivate {
hasUnsavedChanges = false ;
canDeactivate () : boolean {
return ! this . hasUnsavedChanges ;
}
}
// In routes
export const routes : Routes = [
{
path: 'edit/:id' ,
component: EditFormComponent ,
canDeactivate: [ unsavedChangesGuard ],
},
];
Route parameters
Path parameters
export const routes : Routes = [
{
path: 'user/:id' ,
component: UserDetailComponent ,
},
];
Access in component:
import { Component , inject } from '@angular/core' ;
import { ActivatedRoute } from '@angular/router' ;
@ Component ({
selector: 'app-user-detail' ,
template: `<h1>User ID: {{ userId }}</h1>`
})
export class UserDetailComponent {
route = inject ( ActivatedRoute );
userId = this . route . snapshot . paramMap . get ( 'id' );
// Or reactive approach
userId$ = this . route . paramMap . pipe (
map ( params => params . get ( 'id' ))
);
}
Query parameters
import { Component , inject } from '@angular/core' ;
import { ActivatedRoute , Router } from '@angular/router' ;
@ Component ({
selector: 'app-search' ,
template: `<input (input)="search($event)" />`
})
export class SearchComponent {
route = inject ( ActivatedRoute );
router = inject ( Router );
ngOnInit () {
// Read query params
this . route . queryParamMap . subscribe ( params => {
const query = params . get ( 'q' );
console . log ( 'Search query:' , query );
});
}
search ( event : Event ) {
const query = ( event . target as HTMLInputElement ). value ;
// Update query params
this . router . navigate ([], {
relativeTo: this . route ,
queryParams: { q: query },
queryParamsHandling: 'merge' ,
});
}
}
Programmatic navigation
import { Component , inject } from '@angular/core' ;
import { Router } from '@angular/router' ;
@ Component ({
selector: 'app-example' ,
template: `
<button (click)="goToProfile()">Profile</button>
<button (click)="goToUser(123)">User 123</button>
<button (click)="goBack()">Back</button>
`
})
export class ExampleComponent {
router = inject ( Router );
goToProfile () {
this . router . navigate ([ '/profile' ]);
}
goToUser ( id : number ) {
this . router . navigate ([ '/user' , id ]);
}
goToUserWithQuery ( id : number ) {
this . router . navigate ([ '/user' , id ], {
queryParams: { tab: 'settings' }
});
}
goBack () {
window . history . back ();
}
}
Route data and resolvers
Static route data
export const routes : Routes = [
{
path: 'about' ,
component: AboutComponent ,
data: { title: 'About Us' , showHeader: true },
},
];
Access in component:
import { Component , inject } from '@angular/core' ;
import { ActivatedRoute } from '@angular/router' ;
@ Component ({
selector: 'app-about' ,
template: `<h1>{{ title }}</h1>`
})
export class AboutComponent {
route = inject ( ActivatedRoute );
title = this . route . snapshot . data [ 'title' ];
}
Resolver for pre-fetching data
import { inject } from '@angular/core' ;
import { ResolveFn } from '@angular/router' ;
import { HttpClient } from '@angular/common/http' ;
export const userResolver : ResolveFn < User > = ( route ) => {
const http = inject ( HttpClient );
const id = route . paramMap . get ( 'id' );
return http . get < User >( `/api/users/ ${ id } ` );
};
// In routes
export const routes : Routes = [
{
path: 'user/:id' ,
component: UserComponent ,
resolve: { user: userResolver },
},
];
// In component
export class UserComponent {
route = inject ( ActivatedRoute );
user = this . route . snapshot . data [ 'user' ];
}
Best practices
Use lazy loading for feature modules
Split large applications into lazy-loaded modules to reduce initial bundle size
Apply guards at parent level
Protect multiple child routes by applying guards to the parent route
Use resolvers for critical data
Pre-fetch data with resolvers to ensure it’s available when the component initializes
Keep routes organized
Group related routes and use separate route files for large feature areas
Use typed route parameters
Create interfaces for route data and parameters to maintain type safety