In Angular, how would you define routes for a blog with /posts, /posts/:slug and a 404 page, and does their order matter?
answer
- a Routes array passed to provideRouter
- colon marks a parameter
- first match wins
- specific before general
- double asterisk goes last
basics
~10 sRegister a Routes array with provideRouter: 'posts', 'posts/:slug' and a final '**' route for the 404 page. Order matters: the router uses the first route that matches, so the wildcard goes last.
solid answer
~50 sI export `routes: Routes` from `app.routes.ts` and register it with `provideRouter(routes)` in `ApplicationConfig`. The entries are `{ path: 'posts', component: PostList }`, `{ path: 'posts/:slug', component: PostDetail }` — the colon makes `slug` a route parameter matching exactly one URL segment — an empty-path redirect `{ path: '', redirectTo: 'posts', pathMatch: 'full' }`, and last `{ path: '**', component: NotFound }`. Paths never start with a slash. Order matters because the router walks the array top to bottom and uses the first route that matches the whole URL: a `**` placed first would catch everything, and a parameter route listed before a static one with the same shape, like `posts/:slug` before `posts/new`, would swallow it. A leaf route must consume the entire remaining URL, so `posts` does not match `/posts/hello`; the router moves on to `posts/:slug`.
code
ts · 18 linesimport { ApplicationConfig } from '@angular/core';
import { provideRouter, Routes } from '@angular/router';
import { PostList } from './posts/post-list';
import { NewPost } from './posts/new-post';
import { PostDetail } from './posts/post-detail';
import { NotFound } from './not-found';
export const routes: Routes = [
{ path: '', redirectTo: 'posts', pathMatch: 'full' },
{ path: 'posts', component: PostList, title: 'All posts' },
{ path: 'posts/new', component: NewPost, title: 'Write a post' }, // static before :slug
{ path: 'posts/:slug', component: PostDetail },
{ path: '**', component: NotFound, title: 'Page not found' }, // always last
];
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)],
};go deeper
Recall the Routes array, the colon parameter syntax, provideRouter, and that the ** route goes last.
Explain first match wins, why leaf routes must consume the whole URL, and why static paths go before parameter paths of the same shape.
Review a large route table for shadowed routes, choose between rendering and redirecting the 404, and keep ordering rules explicit in code review.
Treat URLs as a public contract: plan redirects for renamed paths and decide how not-found pages behave for users and crawlers.
## The pieces of a route table In Angular's router, a **route** is a plain object of type `Route`; the table is an array typed `Routes` from `@angular/router`. The most common properties are: - **`path`** — the URL pattern, written **without a leading slash**. Segments are separated by `/`; a segment starting with `:` is a **route parameter** that matches exactly one URL segment. - **`component`** — the standalone component rendered in the `<router-outlet>` when the route is active. - **`redirectTo`** — send the navigation somewhere else instead of rendering. - **`pathMatch`** — `'prefix'` (default) or `'full'`, which matters for redirects and routes with children. - **`title`** and **`data`** — the page title and arbitrary static metadata. The table is registered with `provideRouter(routes)` in `ApplicationConfig`; the CLI generates `app.routes.ts` for it. ## The blog table ```ts export const routes: Routes = [ { path: '', redirectTo: 'posts', pathMatch: 'full' }, { path: 'posts', component: PostList, title: 'All posts' }, { path: 'posts/new', component: NewPost, title: 'Write a post' }, { path: 'posts/:slug', component: PostDetail }, { path: '**', component: NotFound, title: 'Page not found' }, ]; ``` | URL | Route that renders | Why | |---|---|---| | `/` | redirect, then `posts` | the empty path with `pathMatch: 'full'` matches only the empty URL | | `/posts` | `PostList` | static path, whole URL consumed | | `/posts/new` | `NewPost` | listed before the parameter route, so it wins | | `/posts/hello-angular` | `PostDetail`, `slug = 'hello-angular'` | one segment after `posts` | | `/posts/2026/hello` | `NotFound` | `:slug` matches one segment only; nothing else fits | | `/about` | `NotFound` | nothing earlier matches | ## How the router picks a route The router walks the array **in order** and takes the **first** route that can match the URL: 1. It compares the route's `path` with the start of the remaining URL segments. 2. A route without children must consume **all** remaining segments; if something is left over, that route fails and the router tries the next one. 3. A route with children consumes its part and hands the rest to its children; if they fail, the router backs off and continues with the next sibling. 4. The first route that succeeds wins; later routes are never considered, even if they would also match. That is why `posts` does not steal `/posts/hello-angular` even though its default `pathMatch` is `'prefix'`: it has no children to consume `hello-angular`, so it fails and the router moves on. It is also why **static before dynamic** matters when both shapes fit the same URL: with `posts/:slug` above `posts/new`, the URL `/posts/new` would render `PostDetail` with `slug = 'new'`. ## The wildcard `path: '**'` matches any URL, including one with many segments. Because the first match wins, it belongs **at the end** of its routes array; anywhere else it hides every route after it. There are two ways to use it: - `{ path: '**', component: NotFound }` renders the 404 page **at the URL the user typed**, which is usually what users and crawlers expect. - `{ path: '**', redirectTo: '/not-found' }` sends them to a dedicated URL, which loses the original address from the address bar. Since v21.1 a `**` can also sit inside a longer path with fixed segments after it; a path containing more than one `**` never matches. ## Parameter names and pitfalls - Parameter names start with a letter and may contain letters, digits, `_` and `-`. - A parameter never spans segments: `posts/:slug` does not match `/posts/a/b`. - A leading slash in `path`, a duplicated comma that leaves an `undefined` entry, or a route with neither `component`, `loadComponent`, `children`, `loadChildren` nor `redirectTo` is rejected by the router's configuration check in development builds. ## What interviewers listen for A correct table, the colon syntax, `pathMatch: 'full'` on the empty redirect, the wildcard last, and the explanation of **first match wins** with the "leaf routes must consume the whole URL" rule that makes the order of `posts` and `posts/:slug` harmless while `posts/new` versus `posts/:slug` is not.
- Should the 404 route render a component or redirect to /not-found?Rendering `NotFound` on the `**` route keeps the URL the user typed, so they can see and fix it, and it is usually what you want. Redirecting to `/not-found` replaces the address, which loses that information. Either way the route must be the last entry in its array.
- Does { path: 'posts' } without pathMatch: 'full' capture /posts/hello?No. A route without children has to consume the entire remaining URL. `posts` consumes one segment, `hello` is left over and there are no children to take it, so the route fails and the router tries the next one, `posts/:slug`.
The router reads the route table like a receptionist reading a list of rules from the top: the first rule that fits the visitor decides the room, so the catch-all rule "everyone else goes to reception" has to be the last line.
saying these in an interview costs you the question
- Paths should start with a slash like '/posts'
- The router picks the most specific route regardless of order
- The wildcard route can go anywhere in the array
- posts/:slug also matches /posts/2026/hello
- A leaf route with default pathMatch captures any longer URL