Router Improvements
Angular 22 updates the router with inherited params by default, withComponentInputBinding options, and browserUrl on router links.
1. Params Inherited by Default
paramsInheritanceStrategy now defaults to 'always'. Child routes automatically receive parameters from all parent routes without any configuration.
// Angular 22: paramsInheritanceStrategy defaults to 'always'
// No configuration needed! Child routes inherit parent params.
// Route config:
{
path: 'teams/:teamId',
children: [{
path: 'members/:memberId',
component: RouteChild,
// In Angular 22, RouteChild gets BOTH teamId AND memberId
// Previously only memberId was available without config
}]
}
// In RouteChild component:
readonly teamId = input<string>(); // ← inherited from parent!
readonly memberId = input<string>(); // ← own route param2. withComponentInputBinding Options
The withComponentInputBinding() feature now accepts an options parameter with unmatchedInputBehavior and queryParams controls.
Try these links and observe the tab input in the child view above.
tab=settings&role=admin Only unmatched param: role=viewer (no tab) No query params at all What to observe
- With
tab=settings: Thetabinput shows"settings" - Without
tab: The input becomesundefined(default:alwaysUndefined) roleparam: Ignored — no matching input on the component
With undefinedIfStale
If using unmatchedInputBehavior: 'undefinedIfStale', the tab input would retain its previous value when navigating without tab — it only resets to undefined if the router previously set it.
// Default: all sources bind, unmatched = undefined
provideRouter(routes, withComponentInputBinding())
// Disable query param binding:
provideRouter(routes, withComponentInputBinding({
queryParams: false,
}))
// Only undefined if previously set by router:
provideRouter(routes, withComponentInputBinding({
unmatchedInputBehavior: 'undefinedIfStale',
}))3. browserUrl on Router Links
browserUrl on routerLink lets you display a different URL in the browser than the actual route. Useful for "pretty URLs" or vanity paths.
The second link navigates to the real route but the browser address bar shows the vanity URL.
<!-- Normal navigation -->
<a routerLink="/teams/1/members/2">Member 2</a>
<!-- Navigate to real route but show a vanity URL -->
<a routerLink="/teams/1/members/2"
[browserUrl]="'/team-alpha/member-2'">
Member 2 (vanity URL)
</a>
<!-- Browser shows: /team-alpha/member-2 --> 4. RemovedprovideRoutes()
provideRoutes() has been removed. Use provideRouter() for top-level configuration or the ROUTES multi token for dynamic/lazy route registration.
provideRoutes(childRoutes)provideRouter(routes) or { provide: ROUTES, useValue: routes, multi: true }// BEFORE (Angular ≤21) , provideRoutes()
import { provideRoutes } from '@angular/router';
providers: [provideRoutes(childRoutes)] // ❌ Removed
// AFTER (Angular 22) , use provideRouter() or ROUTES token
import { provideRouter, ROUTES } from '@angular/router';
// Option 1: provideRouter (recommended)
provideRouter(routes)
// Option 2: ROUTES multi token (for lazy/dynamic routes)
{ provide: ROUTES, useValue: childRoutes, multi: true } 5. ChangedCanMatchFn
The currentSnapshot parameter in CanMatchFn is now required. This gives you access to the current route state for conditional matching.
(route, segments) => boolean(route, segments, currentSnapshot) => boolean// BEFORE (Angular ≤21) , currentSnapshot was optional
const canMatch: CanMatchFn = (route, segments) => {
// currentSnapshot not used
return true;
};
// AFTER (Angular 22) , currentSnapshot is now required
const canMatch: CanMatchFn = (route, segments, currentSnapshot) => {
// currentSnapshot gives access to the current route state
// useful for conditional matching based on current URL
return currentSnapshot.url.length > 0;
}; 6. StricterTitleStrategy
TitleStrategy.getResolvedTitleForRoute() now returns string | undefined instead of any, providing better type safety for custom title strategies.
getResolvedTitleForRoute(): anygetResolvedTitleForRoute(): string | undefined// BEFORE (Angular ≤21) , returned any
class MyTitleStrategy extends TitleStrategy {
override getResolvedTitleForRoute(snapshot: ActivatedRouteSnapshot): any {
return snapshot.data['title'];
}
}
// AFTER (Angular 22) , returns string | undefined
class MyTitleStrategy extends TitleStrategy {
override getResolvedTitleForRoute(
snapshot: ActivatedRouteSnapshot
): string | undefined {
return snapshot.data['title'] as string | undefined;
}
override updateTitle(snapshot: RouterStateSnapshot): void {
const title = this.buildTitle(snapshot);
if (title) document.title = `My App | ${title}`;
}
}Summary
paramsInheritanceStrategy: 'always'is now the default. Set to'emptyOnly'to restore previous behavior.withComponentInputBinding(options), NewunmatchedInputBehaviorandqueryParamsoptions.[browserUrl], New input onrouterLinkfor vanity URLs.provideRoutes(), Removed. UseprovideRouter()orROUTEStoken.CanMatchFn,currentSnapshotparameter is now required.TitleStrategy.getResolvedTitleForRoute(), Returnsstring | undefinedinstead ofany.