Routing-ul modern în Angular e funcțional și lazy implicit: rutele sunt obiecte simple, paginile se încarcă cu loadComponent/loadChildren, iar tot ce vine din URL (parametri, query, data, rezultatele resolverelor) ajunge direct în input()-urile componentei. Fără ActivatedRoute, fără subscribe.
Configurarea
export const routes: Routes = [
{ path: '', loadComponent: () => import('./features/home/home-page') }, // o pagină
{ path: 'products', loadChildren: () => import('./features/products/products.routes') }, // un feature
{ path: 'old-products', redirectTo: 'products' },
{ path: '**', loadComponent: () => import('./core/layout/not-found'), title: 'Pagină inexistentă' },
];
// app.config.ts
provideRouter(
routes,
withComponentInputBinding(), // params / query / data / resolve → input()
withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }),
withViewTransitions(), // animații între pagini (View Transitions API)
// withPreloading(PreloadAllModules),
);Cum alege router-ul o rută
Router-ul citește rutele de sus în jos și o ia pe prima care se potrivește cu tot URL-ul. Un segment :id se potrivește cu orice, ** cu tot, iar redirectTo repornește căutarea cu URL-ul nou. De aceea contează ordinea rutelor.
/books/new → NewBook
- ✗ '' pathMatch 'full', dar a rămas 'books/new'
- ✗ 'books' începutul se potrivește, dar rămâne 'new'
- ✓ 'books/new' se potrivește cu tot URL-ul
Cu books/:id deasupra lui books/new, pagina pentru o carte nouă nu mai poate fi atinsă niciodată: „new” devine un id. Întâi căile specifice, apoi parametrii, ** la final. Cele două greșeli de configurație nici nu ajung până aici: router-ul refuză rutele la pornirea aplicației, cu aceleași mesaje NG04014 pe care le vezi aici.
// întâi căile specifice, apoi parametrii, wildcard-ul la final
export const routes: Routes = [
{ path: '', redirectTo: 'books', pathMatch: 'full' }, // '' + redirect: mereu pathMatch: 'full'
{ path: 'books', loadComponent: () => import('./books/book-list') },
{ path: 'books/new', loadComponent: () => import('./books/new-book') }, // înaintea lui :id, altfel 'new' devine un id
{ path: 'books/:id', loadComponent: () => import('./books/book-detail') },
{ path: 'books/:id/edit', loadComponent: () => import('./books/edit-book') },
{ path: 'about', loadComponent: () => import('./about') }, // fără slash la începutul lui path
{ path: '**', loadComponent: () => import('./not-found') }, // la final: tot ce n-a prins nimeni
];Parametri ca input-uri (live)
Demo-ul de mai jos folosește rute copil reale ale acestei pagini: /routing/user/:id. Fișierul de rute al topicului (routing.routes.ts) e încărcat lazy, iar componenta copil primește totul prin input().
Alege un utilizator din stânga. URL-ul se schimbă, componenta copil primește :id ca 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, moștenit de la PĂRINTE
readonly user = httpResource<UserDto>(() => `/api/users/${this.id()}`);
}Moștenirea parametrilor — schimbare în v22 v22
// v22 (breaking): paramsInheritanceStrategy are acum valoarea implicită 'always'
// → rutele copil moștenesc params, data și resolve de la TOȚI părinții.
{
path: 'orgs/:orgId',
data: { area: 'admin' },
children: [
{ path: 'users/:userId', component: OrgUser }, // OrgUser primește orgId + userId + area
],
}
// Comportamentul vechi (moștenire doar de la părinți fără componentă / cu path gol):
provideRouter(routes, withRouterConfig({ paramsInheritanceStrategy: 'emptyOnly' }));Dacă o rută copil are un parametru cu același nume ca un părinte, sau dacă te bazai pe faptul că un copil NU vede data părintelui, verifică după upgrade. Pentru comportamentul vechi: paramsInheritanceStrategy: 'emptyOnly'.
Navigare și link-uri
<a routerLink="/products">Produse</a>
<a [routerLink]="['/products', product().id]">Detalii</a>
<a [routerLink]="['user', 2]" [queryParams]="{ tab: 'activitate' }">Relativ + query</a>
<a routerLink="/products" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Activ exact</a>
<router-outlet />Când DA, când NU
- Ecran care trebuie să aibă link propriu (partajabil, bookmark, back button).
- Detaliu de entitate:
/products/42. - Tab-uri importante, pe care vrei să le poți deschide direct:
/settings/security. - Filtre și paginare partajabile → query params.
- Stare efemeră de UI: dropdown, hover, dialog de confirmare.
- Pași dintr-un wizard care nu au sens deschiși direct.
- Panouri secundare mici →
@if+ semnal.
Capcane frecvente
- Uiți
withComponentInputBinding()→ input-urile rămânundefined. - Parametrii sunt string-uri: folosește
transform: numberAttributepentru id-uri numerice. - Componenta nu se recreează la navigarea
/user/1→/user/2(aceeași rută): de aceea input-urile sunt semnale — totul se recalculează singur. - Rute eager (
component: Xînapp.routes.ts) pentru pagini mari → tot codul în bundle-ul inițial. Migrare:ng g @angular/core:route-lazy-loading.