Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Cara Beverage
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Cara Beverage@caraphernalia )

Accessing Parent Route Params Via paramsInheritanceStrategy In Angular 6.0.7

By Ben Nadel on

Yesterday, I took a quick look at how to access any and all route parameters in an Angular 6.0.7 application by walking the Router State tree and aggregating all params in an RxJS Observable. In response to that post, Danny Blue pointed out that recent releases of the Angular Router include a "paramsInheritanceStrategy" configuration option that allows a route segment's "params" collections to inherit from its parent route segment(s). Since I had not heard of this feature before, I wanted to try and refactor yesterday's post to showcase this param-inheritance behavior.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

According to the Angular documentation, the paramsInheritanceStrategy Router configuration option can have one of two values:

  • emptyOnly (the default behavior) - Only inherits parent params for path-less or component-less routes.
  • always - enables unconditional inheritance of parent params.

To explore this behavior, I duplicated my demo from yesterday and then added a few more path-parameters in my Route configuration. Then, I added some dynamic logic that would set the "paramsInheritanceStrategy" property based on the current URL. This way, I could jump back and forth between the two versions of the application and compare the various console-logs.

Here's the App Module for this new demo:

  • // 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 { PrimaryDetailViewComponent } from "./views/primary-detail-view.component";
  • import { PrimaryListViewComponent } from "./views/primary-list-view.component";
  • import { PrimaryViewComponent } from "./views/primary-view.component";
  • import { SecondaryDetailViewComponent } from "./views/secondary-detail-view.component";
  • import { SecondaryListViewComponent } from "./views/secondary-list-view.component";
  • import { SecondaryViewComponent } from "./views/secondary-view.component";
  • import { TertiaryDetailViewComponent } from "./views/tertiary-detail-view.component";
  • import { TertiaryListViewComponent } from "./views/tertiary-list-view.component";
  • import { TertiaryViewComponent } from "./views/tertiary-view.component";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • var routes: Routes = [
  • {
  • path: "app",
  • children: [
  • {
  • path: "primary/:primaryID",
  • component: PrimaryViewComponent,
  • children: [
  • {
  • path: "",
  • pathMatch: "full",
  • component: PrimaryListViewComponent
  • },
  • {
  • path: "detail/:primaryDetailID",
  • component: PrimaryDetailViewComponent
  • }
  • ]
  • },
  • {
  • outlet: "secondary",
  • path: "secondary/:secondaryID",
  • component: SecondaryViewComponent,
  • children: [
  • {
  • path: "",
  • pathMatch: "full",
  • component: SecondaryListViewComponent
  • },
  • {
  • path: "detail/:secondaryDetailID",
  • component: SecondaryDetailViewComponent
  • }
  • ]
  • },
  • {
  • outlet: "tertiary",
  • path: "tertiary/:tertiaryID",
  • component: TertiaryViewComponent,
  • children: [
  • {
  • path: "",
  • pathMatch: "full",
  • component: TertiaryListViewComponent
  • },
  • {
  • path: "detail/:tertiaryDetailID",
  • component: TertiaryDetailViewComponent
  • }
  • ]
  • }
  • ]
  • },
  •  
  • // 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"
  • }
  • ];
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @NgModule({
  • bootstrap: [
  • AppComponent
  • ],
  • imports: [
  • BrowserModule,
  • RouterModule.forRoot(
  • routes,
  • {
  • // Tell the router to use the HashLocationStrategy.
  • useHash: true,
  •  
  • // We're going to dynamically set the param-inheritance strategy based
  • // on the state of the browser location. This way, the user can jump back
  • // and forth between the two different modes.
  • paramsInheritanceStrategy:
  • location.search.startsWith( "?always" )
  • ? "always"
  • : "emptyOnly"
  • }
  • )
  • ],
  • declarations: [
  • AppComponent,
  • PrimaryDetailViewComponent,
  • PrimaryListViewComponent,
  • PrimaryViewComponent,
  • SecondaryDetailViewComponent,
  • SecondaryListViewComponent,
  • SecondaryViewComponent,
  • TertiaryDetailViewComponent,
  • TertiaryListViewComponent,
  • TertiaryViewComponent
  • ],
  • 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 hierarchy of route segments now contains a few more parameters. And, the "paramsInheritanceStrategy" configuration is driven by a substring of the browser's search string.

Now, in order to consume those parameters, I went back and updated all of my detail pages to log out the entire "params" object on the injected ActivatedRoute instance. Here is the "primary detail" view, which is representative of the other two detail views:

  • // Import the core angular services.
  • import { ActivatedRoute } from "@angular/router";
  • import { Component } from "@angular/core";
  • import { Params } from "@angular/router";
  • import { Subscription } from "rxjs";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @Component({
  • selector: "primary-detail-view",
  • styleUrls: [ "./primary-detail-view.component.less" ],
  • template:
  • `
  • <h2>
  • Primary Detail
  • </h2>
  •  
  • <p>
  • <a routerLink="../../">Back</a>
  • </p>
  •  
  • <p>
  • This is the Primary Detail view for <strong>ID: {{ primaryDetailID }}</strong>.
  • </p>
  • `
  • })
  • export class PrimaryDetailViewComponent {
  •  
  • public primaryDetailID: number;
  •  
  • private activatedRoute: ActivatedRoute;
  • private paramSubscription: Subscription;
  •  
  • // I initialize the primary detail-view component.
  • constructor( activatedRoute: ActivatedRoute ) {
  •  
  • this.activatedRoute = activatedRoute;
  •  
  • this.paramSubscription = null;
  • this.primaryDetailID = 0;
  •  
  • }
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  • // I get called once when the component is being destroyed.
  • public ngOnDestroy() : void {
  •  
  • ( this.paramSubscription ) && this.paramSubscription.unsubscribe();
  •  
  • }
  •  
  •  
  • // I get called once after the inputs have been bound for the first time.
  • public ngOnInit() : void {
  •  
  • this.paramSubscription = this.activatedRoute.params.subscribe(
  • ( params: Params ) : void => {
  •  
  • this.primaryDetailID = +params.primaryDetailID;
  •  
  • console.group( "Primary Detail View" );
  • console.table( params );
  • console.groupEnd();
  •  
  • }
  • );
  •  
  • }
  •  
  • }

As you can see, I'm just subscribing to the "params" object and then logging the emitted collection to the console. Then, I'm unsubscribing from the ActivatedRoute so that I don't create a memory leak.

Now, if I open up the app and navigate to all three detail views with the default "paramsInheritanceStrategy" behavior, we get the following console output:


 
 
 

 
 Default param inheritance strategy in Angular 6.0.7. 
 
 
 

As you can see, each detail view logs only the route parameters associated with its local ActivatedRoute. Now, let's contrast that with the same rendering when the "paramsInheritanceStrategy" is switched to "always":


 
 
 

 
 The  
 
 
 

As you can see, this time, each detail view logs its own route parameters as well as the parameters inherited by its parents. Now, in this demo, I only have one level of [meaningful] parent-child relationship. But, it should be noted that the inheritance strategy will pertain to the entire list of ancestors (ie, parent, grand-parent, great-grand-parent, etc).

It should also be noted that this inheritance strategy is:

  • Linear: A given route segment only has access to its own parameters and the ones that it inherits from its ancestors. A route segment is not granted access to any of the sibling URL Tree segments.
  • Uni-directional: A given route segment inherits from its parents; but, the parents do not gain insight into what parameters a child route segment is using.

Of course, any route segment can use its ActivatedRoute instance or the root Router instance to inspect the rest of the Router State tree. In fact, in our console logging, we can see that the RouterParams service (examined in the previous demo) can still see all route parameters across the entire Router State tree, regardless of the "paramsInheritanceStrategy" configuration.

Because this demo was based on yesterday's demo, I have excluded most of the code. I only wanted to highlight the differences that were influenced by the "paramsInheritanceStrategy" Router configuration option in Angular 6.0.7. That said, this is a pretty cool feature. It's certainly way easier than trying to manually access parent parameters from within a given route segment. This will be very helpful in cases where you need to render a hierarchical view in which the child view needs to access data based on a parent parameter.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

paramsInheritanceStrategy is such a nice addition, I had a path like

widgetA/:id/widgetB/:id/widgetC

And in the widget I had to go through parent and it's grandparent to get the id.

And for every next child, one step would be increased.

Reply to this Comment

@Samiullah,

Yeah, I agree that it is pretty nice. But, I assume you had to go back and change from :id in each param to something more unique and descriptive? Otherwise, they will just override each other, right?

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
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.