Angular 22

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 param

2. withComponentInputBinding Options

The withComponentInputBinding() feature now accepts an options parameter with unmatchedInputBehavior and queryParams controls.

OptionValuesDescription
queryParamstrue (default) / falseWhether query params bind to component inputs
unmatchedInputBehavior'alwaysUndefined' (default)Unmatched inputs always get undefined
unmatchedInputBehavior'undefinedIfStale'Only sets undefined if the input was previously set by the router

Try these links and observe the tab input in the child view above.

What to observe

  • With tab=settings: The tab input shows "settings"
  • Without tab: The input becomes undefined (default: alwaysUndefined)
  • role param: 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.

BeforeprovideRoutes(childRoutes)
AfterprovideRouter(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.

Before(route, segments) => boolean
After(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.

BeforegetResolvedTitleForRoute(): any
AftergetResolvedTitleForRoute(): 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) , New unmatchedInputBehavior and queryParams options.
  • [browserUrl] , New input on routerLink for vanity URLs.
  • provideRoutes() , Removed. Use provideRouter() or ROUTES token.
  • CanMatchFn , currentSnapshot parameter is now required.
  • TitleStrategy.getResolvedTitleForRoute() , Returns string | undefined instead of any.