Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

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

By Ben Nadel 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.



Reader 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.