Angular2: Make route paths case insensitive

Viewed 23094

I have the following routing configuration.

@RouteConfig([
    {
        path: '/home',
        name: 'Homepage',
        component: HomepageComponent,
        useAsDefault: true
    }
)
export class AppComponent {
}

whenever the browser is pointed to /home this route works but not for /Home or any other case variations. How can I make the router to route to the component without caring the case.

thanks

7 Answers

A little fix to Timothy's answer, which doesn't change the case of matched parameter names and values:

import {Route, UrlSegment, UrlSegmentGroup} from '@angular/router';

export function caseInsensitiveMatcher(url: string) {
  return function(
    segments: UrlSegment[],
    segmentGroup: UrlSegmentGroup,
    route: Route
  ) {
    const matchSegments = url.split('/');
    if (
      matchSegments.length > segments.length ||
      (matchSegments.length !== segments.length && route.pathMatch === 'full')
    ) {
      return null;
    }

    const consumed: UrlSegment[] = [];
    const posParams: {[name: string]: UrlSegment} = {};
    for (let index = 0; index < matchSegments.length; ++index) {
      const segment = segments[index].toString().toLowerCase();
      const matchSegment = matchSegments[index];

      if (matchSegment.startsWith(':')) {
        posParams[matchSegment.slice(1)] = segments[index];
        consumed.push(segments[index]);
      } else if (segment.toLowerCase() === matchSegment.toLowerCase()) {
        consumed.push(segments[index]);
      } else {
        return null;
      }
    }

    return { consumed, posParams };
  };
}

Edit: beside the problem explained above, there is another subtle bug which is resolved now. The for loop should iterate over matchSegments instead of segments.

Note: The following works in Angular 4.x

I just use a matcher function:

import { RouterModule, UrlSegment, UrlSegmentGroup, Route } from '@angular/router';

function CaseInsensitiveMatcher(url: string) {
    url = url.toLowerCase();

    return function(
        segments: UrlSegment[],
        segmentGroup: UrlSegmentGroup,
        route: Route
    ) {
        let matchSegments = url.split('/');
        if (
            matchSegments.length > segments.length ||
            (matchSegments.length !== segments.length && route.pathMatch === 'full')
        ) {
            return null;
        }

        let consumed: UrlSegment[] = [];
        let posParams: {[name: string]: UrlSegment} = {};
        for (let index = 0; index < segments.length; ++index) {
            let segment = segments[index].toString().toLowerCase();
            let matchSegment = matchSegments[index];

            if (matchSegment.startsWith(':')) {
                posParams[matchSegment.slice(1)] = segments[index];
                consumed.push(segments[index]);
            }
            else if (segment === matchSegment) {
                consumed.push(segments[index]);
            }
            else {
                return null;
            }
        }

        return { consumed, posParams };
    }
}

With:

@NgModule({
    imports: [
        ...
        RouterModule.forRoot([
            { matcher: CaseInsensitiveMatcher('user/:id'), component: ProfileComponent },
        ])
    ],
    declarations: [
        ...
    ],
    bootstrap: [
        AppComponent
    ]
})

Edit: I just found out that in order to work with AoT compilation, the @NgModule portion would look more like this:

export function profileMatch() {
    return CaseInsensitiveMatcher('user/:id').apply(this, arguments);
}

@NgModule({
    imports: [
        ...
        RouterModule.forRoot([
            { matcher: profileMatch, component: ProfileComponent },
        ])
    ],
    declarations: [
        ...
    ],
    bootstrap: [
        AppComponent
    ]
})

I did this in my app.component.ts:

export class AppComponent implements OnInit {
  constructor(private _route: Router) { }
  public ngOnInit() {
    this._route.events.subscribe(event => {
      if (event instanceof NavigationStart) {
        let url = (<NavigationStart>event).url;
        if (url !== url.toLowerCase()) {
          this._route.navigateByUrl((<NavigationStart>event).url.toLowerCase());
        }
      }
    });
  }
}

and this is needed on router-link to make it active when selected:

<a routerLink="/heroes" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: false }">Heroes</a>

Here is an updated version of LowerCaseUrlSerializer to not lowercase the query parameters.

import { DefaultUrlSerializer, UrlTree } from '@angular/router';

export class LowerCaseUrlSerializer extends DefaultUrlSerializer {
    parse(url: string): UrlTree {
      const path = url.split('?')[0];
      const query = url.split('?')[1] || '';
      return super.parse(path.toLowerCase() + (query !== '' ? `?${query}`: ''));
    }
}
Related