#Configuring routes
A single-page app has one HTML page, but users still expect URLs: they bookmark /users/2, press Back, share a link. The router keeps the URL and the screen in sync. It watches the address bar, finds the route that matches, and puts that route's component on the page; and when your code or a link asks for a new URL, it does the same in reverse and updates the address bar.
The mental model is a lookup table read from top to bottom. Your Routes array is a list of path → component rows. On every navigation the router walks the list, takes the first row that matches, and stops. Everything else in this chapter — params, nesting, lazy loading, guards — is extra columns on those rows.
A navigation is not instant, though. It's a small pipeline: match the URL against the table, run the guards, run the resolvers, then swap the components. That pipeline is what lets a guard redirect to /login before anything renders.
import { Routes } from '@angular/router';
import { authMatchGuard } from './routing/guards';
import { Home, Login, NotFound } from './routing/pages';
import { UserInput } from './routing/users';
export const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: Home, title: 'Home' },
{ path: 'login', component: Login },
{ path: 'users/:id', component: UserInput },
// One lazy component: the file is only downloaded when /reports is first visited.
{ path: 'reports', loadComponent: () => import('./routing/lazy/reports') },
// A lazy route tree: the guard runs first, and the chunk is fetched only if it passes.
{
path: 'admin',
canMatch: [authMatchGuard],
loadChildren: () => import('./routing/lazy/admin.routes'),
},
{ path: '**', component: NotFound },
];app.config.tsimport { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideBrowserGlobalErrorListeners(), provideRouter(routes, withComponentInputBinding())],
};routesis just an array of plain objects typed asRoutes(Route[]). Nothing happens when this file loads — it's data. Paths never start with/; the router rejectspath: '/home'with an NG04014 "path cannot start with a slash" error.provideRouter(routes, …)inappConfigregisters theRouterservice and hands it the table. It runs once at bootstrap (see appConfig and application providers). The extra arguments are features:withComponentInputBinding()lets the router write params into component inputs (see Route parameters); others includewithRouterConfig,withPreloading,withHashLocation,withViewTransitions.- When the browser asks for
/, the router reads the table top to bottom. Row 1 matches the empty path exactly (pathMatch: 'full', see Route order, wildcards and pathMatch), so it redirects to/homeand starts matching again. Row 2 now matches, soHomeis created and itstitlebecomesdocument.title(verified in a test:document.titlewas'Home page'fortitle: 'Home page'). /users/7skips the first three rows and matchesusers/:id, withid = '7'./reportsand/adminpoint at code that isn't in the main bundle yet; see Lazy loading.- Anything else —
/nope/deeper— falls through to**, the wildcard, which is why it must be last.
AppRoutingModule vs provideRouterFrom my notes, techStatus (Angular 16) did the same thing with NgModules. The table is identical; only the wiring differs.
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
// a feature module registers its own rows
@NgModule({
imports: [RouterModule.forChild(adminRoutes)],
})
export class AdminRoutingModule {}export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withComponentInputBinding()),
],
};
// a lazy feature just exports its routes
export default [
{ path: '', component: AdminLayout, children: [/* … */] },
] satisfies Routes;forRoot and forChild didRouterModule.forRoot provided the Router once for the app; forChild added routes without creating a second router. exports: [RouterModule] was what made <router-outlet> and routerLink usable in every template declared in AppModule. In standalone Angular, provideRouter replaces forRoot, a lazy routes file replaces forChild modules, and each component imports RouterOutlet/RouterLink itself (next topic). See NgModules: the old way.