Angular Router maps URL paths to components and manages navigation state. It supports lazy loading (reducing initial bundle size), route guards (protecting routes), resolvers (pre-loading data), and child routes (nested layouts). Understanding the router's configuration and lifecycle hooks is essential for any multi-page Angular app.

Key Points

  • Routes array: { path, component } or { path, loadComponent } for lazy loading standalone components
  • Route params: /users/:id → inject ActivatedRoute → route.params or inject(ActivatedRouteSnapshot)
  • Lazy loading: loadComponent: () => import('./feature.component').then(m => m.FeatureComponent) — separate JS chunk
  • canActivate guard (functional): inject services, return true/false/UrlTree — functional guards with inject() since Angular 15
  • canDeactivate guard: prevent navigation away from unsaved forms — prompt user to confirm
  • Resolver: loads data before route activates — resolve: { user: userResolver } — data available in route.data
  • Child routes / nested outlets: { path: 'admin', children: [...], component: AdminLayoutComponent }
  • Router events: NavigationStart, NavigationEnd — use for loading spinners, analytics
  • withComponentInputBinding(): router params automatically bound to component @Input — Angular 16+

Angular Router: lazy standalone components, functional guards and resolvers, child routes, withComponentInputBinding

// app.routes.ts — Angular 17 functional style
export const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  {
    path: 'home',
    loadComponent: () => import('./home/home.component').then(m => m.HomeComponent)
  },
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin-layout.component').then(m => m.AdminLayoutComponent),
    canActivate: [authGuard],          // functional guard
    children: [
      { path: '', loadComponent: () => import('./admin/dashboard.component').then(m => m.DashboardComponent) },
      { path: 'users/:id', loadComponent: () => import('./admin/user-detail.component').then(m => m.UserDetailComponent),
        resolve: { user: userResolver } }
    ]
  },
  { path: '**', loadComponent: () => import('./not-found.component').then(m => m.NotFoundComponent) }
];

// Functional guard
export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);
  return auth.isLoggedIn()
    ? true
    : router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } });
};

// Functional resolver
export const userResolver: ResolveFn<User> = (route) => {
  return inject(UserService).getUser(route.paramMap.get('id')!);
};

// Component — params via @Input (Angular 16+ withComponentInputBinding)
@Component({ standalone: true, ... })
export class UserDetailComponent {
  @Input() id!: string;          // from route param :id
  @Input() user!: User;          // from resolver

  ngOnChanges() { /* re-load on param change */ }
}

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes,
      withComponentInputBinding(),       // @Input from route params
      withViewTransitions(),             // CSS View Transitions API
      withDebugTracing()                 // verbose router logging (dev only)
    )
  ]
};

Real-World Example

Lazy loading is the most impactful Angular performance optimisation — splitting a 2MB bundle into 200KB initial + 10 feature chunks means users load only what they need. The rule of thumb: every feature route should be lazy. Use --stats-json and webpack-bundle-analyzer to see which modules are in the initial bundle and find accidental eager imports.