Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Scott Van Hess
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Scott Van Hess

A Single Route Parameter Can Match Multiple URL Segments In Angular 4.4.4

By on

UPDATE: If you are parforming a prefix-based, local-direct, you may want to know that the Router will automatically append the non-local URL segments to the redirectTo action, making prefix-based mappings dead simple in Angular 4.4.4.

When I was learning about route configuration in Angular 4, I automatically assumed that a route parameter, like ":id" or ":type", would only match against a single URL path segment (ie, the portion of a URL between two "/" delimiters). As is turns out, however, a route parameter is capable of matching any non-empty portion of the URL. This gives us the ability to redirect based on a URL-prefix, capturing and forwarding the entire URL-suffix using a single route parameter.

Run this demo in my JavaScript Demos project on GitHub.

First off, a huge thanks to Juri Strumpflohner who show me that Angular route parameters work in this way; I did not stumble upon this behavior. In fact, this post is little more than me documenting what he showed me was possible.

That said, to demonstrate the path-capturing capabilities of a route parameter, I've created a simple Angular demo in which we match any route that begins with the "/a" prefix and redirect the user to the "/b" prefix, keeping the rest of the URL in tact. So, for example, if you were to navigate to:

**/a/**foo/bar/baz

... you would be redirected to:

**/b/**foo/bar/baz

Notice that the "..../foo/bar/baz" portion of the URL is maintained while redirecting from "/a" to "/b".

Let's take a look at the route configuration that makes this possible:

// Import the core angular services.
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Routes } from "@angular/router";

// Import the application components and services.
import { AppComponent } from "./app.component";
import { BComponent } from "./b.component";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

var routes: Routes = [
	// We're going to be redirecting any route that begins with "/a" to a route that
	// begins with "/b", keeping the rest of the route in tact. We can do with this
	// route segments which can actually match more than one slash-delimited token. In
	// this case, we're using ":restOfPath" to match the entire remainder of of the
	// route - everything that follows the "/a/" prefix.
	{
		path: "a/:restOfPath",
		redirectTo: "b/:restOfPath"
	},
	// Since the ":restOfPath" path parameter has to match a non-zero-length value, we
	// have to include an additional redirect for the naked "/a" route (if we want to
	// handle those as well).
	{
		path: "a",
		redirectTo: "b"
	},
	{
		path: "b",
		children: [
			// We're going to render all "/b"-prefix routes in the same component.
			{
				path: "**",
				component: BComponent
			}
		]
	}
];

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@NgModule({
	bootstrap: [
		AppComponent
	],
	imports: [
		BrowserModule,
		RouterModule.forRoot(
			routes,
			{
				// Tell the router to use the HashLocationStrategy.
				useHash: true
			}
		)
	],
	declarations: [
		AppComponent,
		BComponent
	],
	providers: [
		// CAUTION: We don't need to specify the LocationStrategy because we are setting
		// the "useHash" property in the Router module above.
		// --
		// {
		// provide: LocationStrategy,
		// useClass: HashLocationStrategy
		// }
	]
})
export class AppModule {
	// ...
}

Notice that we are defining an "/a" path with a single route parameter:

path: a/:restOfPath

In this case, the :restOfPath parameter will match the entire URL suffix following the "a/" pattern. This may contain one URL segment; or, it may contain many URL segments. This route parameter can then be used to append the entire URL suffix to the redirect route:

redirectTo: b/:restOfPath

Since a route parameter can only match a non-empty value (as far as I know), we need to include a second redirect to handle the case in which the "/a" path is matched without any suffix at all. This way, we can redirect from a naked "/a" request to a naked "/b" resolution.

To play around with this route parameter redirection, I've created an app component that provides a variety of "/a"-prefixed links:

// Import the core angular services.
import { Component } from "@angular/core";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	selector: "my-app",
	styleUrls: [ "./app.component.css" ],
	template:
	`
		<p>
			Try going to one of these <code>/a</code> prefix routes
			(which do not have materialized views):
		</p>

		<ul>
			<li><a routerLink="/a">/a</a></li>
			<li><a routerLink="/a/items">/a/items</a></li>
			<li><a routerLink="/a/items/4">/a/items/4</a></li>
			<li><a routerLink="/a/items/4/detail">/a/items/4/detail</a></li>
			<li><a routerLink="/a/items/4/detail" fragment="anchor">/a/items/4/detail#anchor</a></li>
			<li><a routerLink="/a/items/4/detail" [queryParams]="{ q: '1' }">/a/items/4/detail?q=1</a></li>
		</ul>

		<router-outlet></router-outlet>
	`
})
export class AppComponent {
	// ...
}

As you can see, I'm testing out several ways in which to construct an HREF value, including paths, query-string parameters, and anchors. All of these will be matched by our route configuration and redirected from the "/a" URL tree to the "/b" URL tree, which is rendered by the BComponent:

// Import the core angular services.
import { Component } from "@angular/core";
import { Event as NavigationEvent } from "@angular/router";
import { Router } from "@angular/router";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	styleUrls: [ "./b.component.css" ],
	template:
	`
		<h3>
			B-Prefix Component
		</h3>

		<p>
			You have navigated to <code>{{ url }}</code>
		</p>
	`
})
export class BComponent {

	public url: string;

	// I initialize the B-view component.
	constructor( router: Router ) {

		// As the user navigates through the "/a"-prefix routes, they will all be
		// redirected to the "/b"-prefix routes that are rendered by this component. As
		// that happens, this component will persist since we never navigate away from
		// it. As such, we have to listen for navigation events to know when to update
		// the view.
		// --
		// NOTE: We could have also listened to the ActivatedRoute.
		router.events.subscribe(
			( event: NavigationEvent ) : void => {

				this.url = router.url;

			}
		);

	}

}

Here's we're just listening for navigation events and then echoing the current URL in the BComponent view. And, when we run this Angular application and click on one of the "/a" prefix links, we get the following output:

Rotue parameters can match multiple url path segments, allowing for prefix-based redirects in Angular 4.

As you can see, when we click on any of the URLs that start with the "/a" prefix, we are redirected to the "/b" prefix; and, the rest of the URL is passed along in the redirect, thanks to the :restOfPath route parameter.

When you start out by extracting entity IDs from URLs using route parameters like ":id" or ":userID", it's easy to believe that route parameters can only match a single segment in the URL path. But, in Angular, a route parameter can match multiple URL path segments. This allows us to capture entire URL suffixes and even forward those suffix values as part of Router redirect operations.

Want to use code from this post? Check out the license.

Reader Comments

15,260 Comments

@All,

It turns out, if you are doing a LOCAL-only redirect, this becomes an even easier problem - you just need to redirect the prefix, and the rest of the non-LOCAL URL segments are automatically appended to the redirectTo action:

www.bennadel.com/blog/3348-local-redirects-automatically-append-the-non-local-route-segments-in-angular-4-4-4.htm

That said, if you are doing an absolute redirect (starting with "/"), then you would still need this route-parameter approach.