Local Redirects Automatically Append The Non-Local Route Segments In Angular 4.4.4
Yesterday, Juri Strumpflohner taught me that a single route parameter can match multiple URL segments in Angular 4.4.4. I was planning on using this behavior to perform prefix-based redirects in my Angular application. But, as it turns out, I don't need to do this - at least not for local redirects (as opposed to absolute redirects). With a local redirect, any non-local route segments are automatically appended to the redirected route. This makes prefix-based redirecting almost too easy!
Run this demo in my JavaScript Demos project on GitHub.
The route configurations in Angular are defined using a hierarchy of partial URL patterns. At each level of this hierarchy, you can define static URL segments and value-capturing route parameters. You can also redirect to absolute locations (ie, redirects that begin with a "/"); or, you can redirect to local locations.
When redirecting to a local location within the hierarchy (ie, changing the routing behavior at-and-below the current routing node), the remaining, non-local route segments (ie, those not matched by the current configuration node) are automatically appended to the redirection. Or, in other words, a local redirect only affects the local portion of the overall route:
As you can see, the only part of the URL affected by the "redirectTo" is the segment of the URL matched by the given node in the configuration hierarchy. This behavior makes it incredibly easy to redirect entire portions of the URL hierarchy because Angular will automatically append the remainder of the URL to any local redirect.
To see this behavior in action, I've taken my demo from yesterday and completely removed the route parameter "sink". Now, in my application module's route configuration, I simply redirect from "/a" to "/b/z", allowing any remaining portions of the URL to be appended automatically by Angular:
// 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 redirect all "/a" routes to the "/b/z" route. When redirecting to
// a RELATIVE path (ie, not absolute), the remainder of the path (not matched by
// the local pattern) will be AUTOMATICALLY appended to the redirect. As such, this
// route configuration will replace any "/a" with "/b/z" in any route that begins
// with the "/a" prefix (no need to create a "sink" route parameter).
{
path: "a",
redirectTo: "b/z"
},
{
path: "b/z",
children: [
// We're going to render all "/b/z"-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 {
// ...
}
And, in my application component, I'm still providing a set of links that point to the "/a" subsystem of locations:
// 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 {
// ...
}
All of these "/a" links will be redirects to the "/b/z" prefix, which is rendered by the BComponent. The BComponent does nothing more than render the URL that's currently being rendered:
// Import the core angular services.
import { ActivatedRoute } from "@angular/router";
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { UrlSegment } from "@angular/router";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
@Component({
styleUrls: [ "./b.component.css" ],
template:
`
<h3>
B/Z-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( activatedRoute: ActivatedRoute, router: Router ) {
// As the user navigates through the "/a"-prefix routes, they will all be
// redirected to the "/b/z"-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 route changes to know when to update
// the view.
activatedRoute.url.subscribe(
( urlSegments: UrlSegment[] ) : void => {
this.url = router.url;
}
);
}
}
Now, if we run this application in the browser and navigate to one of the "/a"-prefix links, we get the following output:
As you can see, when we click on one of the "/a"-prefix links, we are redirected to the "/b/z" route; and, any non-local portion of the remaining URL is automatically appended to the redirectTo action.
How great is that! This router behavior in Angular 4.4.4 should make it incredibly easy to perform prefix-based redirects when you need to map an entire subset of URLs onto another another subset. Of course, this only works for local redirects - if you are performing an absolute redirect, you still need to use a "sink" route-parameter as I did in my previous post.
Want to use code from this post? Check out the license.
Reader Comments