Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Aaron Grewell and Kevin Johnson
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Aaron Grewell and Kevin Johnson

Wildcard Routes (**) Can Redirect Relative To Their UrlTree Location In Angular 5.1.3

By Ben Nadel on

Yesterday, I demonstrated that wildcard routes (**) can be scoped to a route sub-tree in Angular 5.1.3. I described this behavior as good for "modularity" in that it made it possible for a feature module to handle invalid URLs within its own local logic. But, the "redirectTo" route configuration that I used in the demo was anything but "modular" - it redirected to an absolute path in the application. As a quick follow-up post, I wanted to demonstrate that a wildcard route can redirect to a URL that is relative to its place in the UrlTree. This allows feature modules to contain truly modular route definitions in Angular 5.1.3.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

In yesterday's post, I had a "**" catch-all segment that used an absolute URL redirect:

  • /app/a/(path: **) => (redirectTo: "/app/a") => /app/a

This worked; but, of course, coupled my local URL logic to the fact that the entire application was below some "/app" prefix. To decouple the local portion of the URL tree from the app prefix, we can change the "redirectTo" value to use a location-relative path:

  • /app/a/(path: **) => (redirectTo: "") => /app/a
  • /app/a/(path: **) => (redirectTo: "sub") => /app/a/sub

By using a redirectTo value of "" (empty string), we can place the user at the local-root of the router sub-tree. And, by using a redirectTo value of "sub", we can place the user at the "sub" path of the router sub-tree. The point is, the "redirectTo" value can be used to define a segment that is relative to its parent segment.

To see this in action, I've modified the router configuration in yesterday's demo to use a relative path in the "**" catch-all route. Nothing else has changed:

  • // 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 { AViewComponent } from "./a-view.component";
  • import { BViewComponent } from "./b-view.component";
  • import { SubViewComponent } from "./sub-view.component";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • var routes: Routes = [
  • {
  • path: "app",
  • children: [
  • {
  • path: "a",
  • component: AViewComponent,
  • children: [
  • {
  • path: "sub",
  • component: SubViewComponent
  • },
  •  
  • // This is a WILDCARD CATCH-ALL route that is scoped to the "/app/a"
  • // route prefix. It will only catch non-matching routes that live
  • // within this portion of the router tree. And, by redirecting to the
  • // empty string (redirectTo: ""), we'll be leaving the user at the
  • // local root of the "./a" sub-tree. This way, we can catch missing
  • // local routes without having to know the location of the sub-tree
  • // in the overall route architecture.
  • {
  • path: "**",
  • redirectTo: ""
  • // NOTE: Using (redirectTo: "sub") would have redirected to "sub"
  • // route.
  • }
  • ]
  • },
  • {
  • path: "b",
  • component: BViewComponent,
  • children: [
  • {
  • path: "sub",
  • component: SubViewComponent
  • }
  • ]
  • }
  • ]
  • },
  •  
  • // Redirect from the root to the "/app" prefix (this makes other features, like
  • // secondary outlets) easier to implement later on.
  • {
  • path: "",
  • pathMatch: "full",
  • redirectTo: "app"
  • },
  •  
  • // This is the WILDCARD CATCH-ALL route that is scoped to the entire application. It
  • // will catch any request that is not matched by an earlier route definition.
  • {
  • path: "**",
  • redirectTo: "/app"
  • }
  • ];
  •  
  • @NgModule({
  • bootstrap: [
  • AppComponent
  • ],
  • imports: [
  • BrowserModule,
  • RouterModule.forRoot(
  • routes,
  • {
  • // Tell the router to use the HashLocationStrategy.
  • useHash: true,
  • enableTracing: true
  • }
  • )
  • ],
  • declarations: [
  • AppComponent,
  • AViewComponent,
  • BViewComponent,
  • SubViewComponent
  • ],
  • 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 {
  • // ...
  • }

As you can see, the catch-all route (**) is redirecting the user to the "" segment. Which will leave them in the root of the "/app/a" UrlTree. And, if we run this in the browser and try to navigate to the invalid route within the "/app/a" sub-tree, we get the following output:


 
 
 

 
 The wildcard route (**) can redirect to a relative location in the UrlTree. 
 
 
 

As you can see, the use of the relative (redirectTo: "") left the user in the local root of the "/app/a" sub-tree. We were able to handle the feature module redirect logic without coupling the feature module to the overall URL architecture of the application.

I should have mentioned this yesterday. But, to be honest, I didn't even think that this would work. I only thought to test it after I had already posted yesterday's demo. And, when I tried it this morning, I was surprised and delighted that it worked.




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.