Modernes Routing in Angular ist funktional und standardmäßig lazy: Routen sind einfache Objekte, Seiten werden mit loadComponent/loadChildren geladen, und alles, was aus der URL kommt (Parameter, Query, data, Resolver-Ergebnisse), landet direkt in den input()s der Komponente. Kein ActivatedRoute, kein subscribe.
Konfiguration
export const routes: Routes = [
{ path: '', loadComponent: () => import('./features/home/home-page') }, // eine Seite
{ path: 'products', loadChildren: () => import('./features/products/products.routes') }, // ein Feature
{ path: 'old-products', redirectTo: 'products' },
{ path: '**', loadComponent: () => import('./core/layout/not-found'), title: 'Seite nicht gefunden' },
];
// app.config.ts
provideRouter(
routes,
withComponentInputBinding(), // params / query / data / resolve → input()
withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }),
withViewTransitions(), // Animationen zwischen Seiten (View Transitions API)
// withPreloading(PreloadAllModules),
);Wie der Router eine Route auswählt
Der Router liest die Routen von oben nach unten und nimmt die erste, die zur ganzen URL passt. Ein Segment :id passt auf alles, ** auf jede URL, und redirectTo startet die Suche mit der neuen URL von vorn. Deshalb ist die Reihenfolge der Routen wichtig.
/books/new → NewBook
- ✗ '' pathMatch 'full', aber 'books/new' bleibt übrig
- ✗ 'books' der Anfang passt, aber 'new' bleibt übrig
- ✓ 'books/new' passt zur ganzen URL
Steht books/:id über books/new, ist die Seite für ein neues Buch nie erreichbar: „new“ wird zur id. Spezifische Pfade zuerst, Parameter danach, ** zuletzt. Die beiden Konfigurationsfehler kommen gar nicht so weit: Der Router lehnt die Routen beim Start der App ab, mit denselben NG04014-Meldungen wie hier.
// zuerst spezifische Pfade, dann Parameter, der Wildcard zuletzt
export const routes: Routes = [
{ path: '', redirectTo: 'books', pathMatch: 'full' }, // '' + Redirect: immer pathMatch: 'full'
{ path: 'books', loadComponent: () => import('./books/book-list') },
{ path: 'books/new', loadComponent: () => import('./books/new-book') }, // vor :id, sonst wird 'new' zur id
{ path: 'books/:id', loadComponent: () => import('./books/book-detail') },
{ path: 'books/:id/edit', loadComponent: () => import('./books/edit-book') },
{ path: 'about', loadComponent: () => import('./about') }, // kein führender Slash im path
{ path: '**', loadComponent: () => import('./not-found') }, // zuletzt: alles, was sonst niemand erwischt hat
];Parameter als Inputs (live)
Das Beispiel unten verwendet echte Child-Routen dieser Seite: /routing/user/:id. Die Routen-Datei des Themas (routing.routes.ts) wird per Lazy Loading geladen, und die Child-Komponente erhält alles über input().
Wähle links einen Benutzer aus. Die URL ändert sich, und die Child-Komponente erhält :id als Input.
@Component({ /* ... */ })
export class UserDetail {
readonly id = input.required({ transform: numberAttribute }); // :id (path param)
readonly tab = input<string>(); // ?tab= (query param)
readonly section = input<string>(); // data.section
readonly area = input<string>(); // data.area, vom PARENT geerbt
readonly user = httpResource<UserDto>(() => `/api/users/${this.id()}`);
}Parameter erben — Änderung in v22 v22
// v22 (Breaking Change): paramsInheritanceStrategy hat jetzt den Standardwert 'always'
// → Child-Routen erben params, data und resolve von ALLEN Parents.
{
path: 'orgs/:orgId',
data: { area: 'admin' },
children: [
{ path: 'users/:userId', component: OrgUser }, // OrgUser erhält orgId + userId + area
],
}
// Das alte Verhalten (nur von Parents ohne Komponente / mit leerem Pfad erben):
provideRouter(routes, withRouterConfig({ paramsInheritanceStrategy: 'emptyOnly' }));Wenn eine Child-Route einen Parameter mit demselben Namen wie ein Parent hat oder wenn du dich darauf verlassen hast, dass ein Child die data seines Parents NICHT sieht, prüfe das nach dem Upgrade. Für das alte Verhalten: paramsInheritanceStrategy: 'emptyOnly'.
Navigation und Links
<a routerLink="/products">Produkte</a>
<a [routerLink]="['/products', product().id]">Details</a>
<a [routerLink]="['user', 2]" [queryParams]="{ tab: 'activitate' }">Relativ + Query</a>
<a routerLink="/products" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Genau aktiv</a>
<router-outlet />Wann ja, wann nein
- Ein Screen, der einen eigenen Link braucht (teilbar, Lesezeichen, Zurück-Button).
- Detailansicht einer Entität:
/products/42. - Wichtige Tabs, die sich direkt öffnen lassen sollen:
/settings/security. - Teilbare Filter und Paginierung → Query-Parameter.
- Flüchtiger UI-Zustand: Dropdown, Hover, Bestätigungsdialog.
- Wizard-Schritte, die direkt geöffnet keinen Sinn ergeben.
- Kleine Nebenpanels →
@if+ Signal.
Häufige Stolperfallen
- Du vergisst
withComponentInputBinding()→ die Inputs bleibenundefined. - Parameter sind Strings: Verwende
transform: numberAttributefür numerische IDs. - Die Komponente wird nicht neu erzeugt, wenn du von
/user/1zu/user/2navigierst (gleiche Route): Deshalb sind die Inputs Signals — alles wird von selbst neu berechnet. - Eager-Routen (
component: Xinapp.routes.ts) bei großen Seiten → der gesamte Code landet im initialen Bundle. Migration:ng g @angular/core:route-lazy-loading.