Angular Router enables navigation between views, supports deep linking, lazy loading, and provides guards for protecting routes. It is essential for building single-page applications with multiple views.In a traditional multi-page website, clicking a link causes a full page reload — the browser requests a new HTML document from the server. In a Single Page Application (SPA), the router intercepts navigation, swaps out the component displayed on screen, and updates the browser URL — all without a page reload. The result feels instant, like a native app. The Angular Router handles this entire orchestration: matching URLs to components, running security checks (guards), pre-fetching data (resolvers), and managing browser history.What You’ll Learn:
// product-detail.component.ts@Component({ selector: 'app-product-detail', standalone: true, template: `<h1>Product {{ id }}</h1>`})export class ProductDetailComponent { // Route param automatically bound as input! id = input.required<string>(); // Also works with query params sort = input<string>(); // ?sort=price}
Load feature modules only when needed. Lazy loading is one of the highest-impact performance optimizations in Angular. Without it, your entire application — every feature, every admin page, every settings screen — gets downloaded upfront before the user sees anything. With lazy loading, the browser only downloads the code for the page the user is actually visiting. Additional pages load on demand as the user navigates.
Protect routes with functional guards. Guards are the bouncers at the door of your routes — they decide who gets in and who gets redirected. Angular runs guards before the route activates, so the user never sees a flash of unauthorized content.
Common pitfall: Never rely on route guards as your only security layer. Guards run on the client and can be bypassed by a determined user. Always verify permissions on the server side too. Guards are a UX convenience (redirecting unauthorized users to login), not a security mechanism.
Q: A user reports that clicking the browser's back button after navigating through several pages causes the app to show stale data. How would you debug and fix this?
Strong Answer: This is a classic Angular routing issue related to component reuse and data fetching. By default, when you navigate to /products/1 and then /products/2, Angular reuses the ProductDetailComponent instance because the route template is the same (products/:id). The component’s ngOnInit does not re-run because the component is not destroyed and recreated.The fix depends on how you fetch data. If you use route.snapshot.params, you only get the params at the time of the initial navigation — they do not update. You need to subscribe to route.params (an observable) or use signal-based input binding (withComponentInputBinding) to react to parameter changes. With input binding, the id signal input updates automatically when the route changes.For the back button specifically, the issue is compounded because the browser restores the scroll position and previous URL, but Angular’s route resolver or data-fetching logic might not re-trigger. If using resolvers, the data is re-fetched on each navigation. If fetching in the component, you need the reactive params pattern.I would also check if the app uses onSameUrlNavigation: ‘reload’ in the router config, which controls whether navigating to the same URL triggers route processing. And I would verify that any caching layer (shareReplay, service-level cache) invalidates correctly.Follow-up: How does withComponentInputBinding change the way you handle route parameters?
Answer: withComponentInputBinding automatically maps route parameters, query parameters, and resolved data to component inputs with matching names. So if your route has :id and your component has id = input.required<string>(), the value is automatically provided. This eliminates the need to inject ActivatedRoute entirely. The signal input updates reactively when the route changes, so you can use computed() or effect() to react to parameter changes — no manual subscription needed.
Q: Explain the difference between canActivate, canDeactivate, and canMatch guards. Give a real-world scenario where canMatch is the right choice over canActivate.
Strong Answer: canActivate runs after route matching and prevents navigation to a route — the user sees a redirect or error page. canDeactivate runs when leaving a route and can prevent navigation away — the classic “unsaved changes” confirmation. canMatch runs during route matching itself and affects which route definition is selected.The critical difference with canMatch: it controls route matching, not just access. If canMatch returns false, the router does not just block access — it acts as if that route definition does not exist and continues checking the next route definition with the same path.The perfect real-world scenario: role-based dashboards. You have an admin dashboard and a user dashboard, both at /dashboard. With canMatch, you define two route entries for the same path, each with a different canMatch guard checking the user’s role. When an admin navigates to /dashboard, canMatch on the admin route returns true, so they get AdminDashboardComponent. When a regular user navigates to /dashboard, canMatch on the admin route returns false, the router skips it, and matches the next route definition which loads UserDashboardComponent.You cannot do this with canActivate because canActivate runs after matching — it would match the first /dashboard route and then redirect, which changes the URL and creates a worse UX.Follow-up: Should you rely on route guards as a security mechanism?
Answer: Never as the sole mechanism. Route guards are a UX convenience layer. They prevent honest users from accidentally landing on pages they should not see, and they create smooth redirect-to-login flows. But guards run entirely on the client — a determined user can open DevTools, modify the guard’s return value, or directly call your API endpoints. Every authorization check must be enforced server-side. The guard prevents seeing a flash of the admin page; the API prevents actually accessing admin data.
Q: Walk me through how you would implement lazy loading for a large application with 20+ feature modules. What is your strategy for preloading?
Strong Answer: My approach: every feature that is not on the initial landing page gets lazy loaded. This means each feature has its own routes file exported as a constant, and the app.routes.ts uses loadChildren to point to it. The initial bundle only contains the shell (nav, footer), the landing page component, and the core services.For preloading strategy, I would not use PreloadAllModules blindly because it downloads everything immediately after initial load — fine for small apps, wasteful for large ones. Instead, I implement a custom PreloadingStrategy that prioritizes based on likelihood of navigation. For example, if analytics show 80% of users visit the product catalog after landing, I preload that first. Admin modules only preload if the user has an admin role.The implementation: create a class that implements PreloadingStrategy and its preload method. For each route, check a condition (role, analytics data, or a custom route data property like preload: true) and either call load() to preload or return EMPTY to skip.I also use @defer with prefetch on idle for component-level lazy loading within pages. So a product detail page lazy-loads its review section and recommendation carousel independently.The measurable impact: on a project I worked on, we reduced initial bundle from 2.1MB to 380KB by lazy loading features, and then reduced perceived load time further with strategic preloading so navigations to likely-next pages were instant.Follow-up: How do you verify that lazy loading is actually working and not accidentally pulling modules into the main bundle?
Answer: I use the Angular CLI’s ng build —stats-json flag to generate a webpack stats file, then visualize it with webpack-bundle-analyzer or source-map-explorer. These tools show exactly which modules are in which chunk. If a lazy module shows up in the main bundle, it means something in the main bundle imports from it directly — usually a shared barrel export (index.ts) that re-exports everything.