Accessing Parent Route Params Via paramsInheritanceStrategy In Angular 6.0.7
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:
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":
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.
Want to use code from this post? Check out the license.
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.
@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?I'v been stuck in this situation at work for something like 1 week searching without success for any solution, Thank you so much for the post, it helped me a lot afront others milions searching on StakeOverFlow
@Vinícius,
Ha ha, woot woot! That's the best feeling -- glad I was able to help :D