Route Config is an Angular library that provides tools to easily set and access the properties defined in RouterModule configuration. It offers some built in tools that work out of the box but also is easily extensible via data
property of Angular's Route configuration object.
It supports:
✅ Displaying parts of component's template based on the tags defined in the Router config
✅ Retrieving custom properties defined in currently rendered route
✅ Type safety for custom properties
Install the package:
npm install @this-dot/route-config
or
yarn add @this-dot/route-config
It is very simple to add route-config to your Angular app:
Just import the RouteConfigModule
module
import { RouteConfigModule } from '@this-dot/route-config';
and add it to the imports array in the Angular module
@NgModule({
/* other module props */
imports: [RouteConfigModule.forRoot() /* other modules */],
})
export class AppModule {}
The library's elements use Angular's router data
object to configure the behavior. See the below examples on how to use it in your application.
To use library's provided directives and/or pipes just add RouteConfigModule
in your submodule that uses them. E.g.
@NgModule({
/* other module props */
imports: [RouteConfigModule /* other modules */],
})
export class YourSubModule {}
To configure this directive lets create the following sample router configuration:
@NgModule({
declarations: [FirstRouteComponent, SecondRouteComponent],
imports: [
RouterModule.forRoot([
{
path: 'first',
component: FirstRouteComponent,
data: {
routeTags: ['show'],
},
},
{
path: 'second',
component: SecondRouteComponent,
},
]),
],
exports: [RouterModule],
})
export class AppModule {}
Now we can use it in the component's template
<p *tdRouteTag="'show'">
This text is only visible, if there is a 'show' tag in the route data's `routeTags` Array
</p>
*tdRouteTag
provides a way do display a fallback template if a given tag is not present
<p *tdRouteTag="'show'; else noShowTag">
This text is only visible, if there is a 'show' tag in the route data's `routeTags` Array
</p>
<ng-template #noShowTag>
<p>There is no 'show' tag in this route's config</p>
</ng-template>
If you need to use a different route data property to store the tags, you can use the *tdRouteDataHas
directive. It works very similar to *tdRouteTag
directive but provides a way to use different properties as a source of data.
Let's take a look at the following router configuration:
@NgModule({
declarations: [FirstRouteComponent, SecondRouteComponent],
imports: [
RouterModule.forRoot([
{
path: 'first',
component: FirstRouteComponent,
data: {
customDataProperty: ['customShow'],
},
},
{
path: 'second',
component: SecondRouteComponent,
},
]),
],
exports: [RouterModule],
})
export class AppModule {}
Now to configure the directive to use the customDataProperty
property we can use tdRouteDataHasPropName
input to set the desired property name:
<p *tdRouteDataHas="'customShow'; propName: 'customDataProperty'">
This text is only visible, if there is a 'show' tag in the route data's `customDataProperty` Array
</p>
*tdRouteDataHas
also provides a way do display a fallback template if a given tag is not present
<p *tdRouteDataHas="'customShow'; propName: 'customDataProperty'; else noShowTag">
This text is only visible, if there is a 'customShow' tag in the route data's `customDataProperty` Array
</p>
<ng-template #noShowTag>
<p>There is no 'customShow' tag in this route's config</p>
</ng-template>
This directive allows for access to the whole data
property defined in the current Route from a Component's template.
We can use it as following:
<h1 *tdRouteData="let data">
Current title is: {{ data.title }}
</h1>
It is also possible to pass a default value so that if a property is not defined in the Route we will still receive some value:
<h1 *tdRouteData="let data; defaultValue: { title: 'DefaultTitle', routeTags: ['defaultTag'] }">
Current title is: {{ data.title }}
</h1>
If you want to access multiple properties in one component's template it is recommended to wrap the whole template with only one *tdRouteData
directive. This approach follows DRY principle and is efficient as it only creates one subscription per template.
<ng-container *tdRouteData="let data; defaultValue: { title: 'DefaultTitle', routeTags: ['defaultTag'] }">
<h1>
Current title is: {{ data.title }}
</h1>
<p>
Current route contains the following tags: {{ data.routeTags | json }}
</p>
</ng-container>
In every component you can inject RouteConfigService
to get the current route configuration properties.
export class AppComponent {
constructor(private routeConfigService: RouteConfigService) {}
}
You can use getLeafConfig
method to get the Observable with current route's property value
export class AppComponent {
tags$ = this.routeConfigService.getLeafConfig('routeTags', ['no tags']);
}
Now you can treat it as any other Observable and use e.g. async
pipe to display the current value
<h1>{{ tags$ | async }}</h1>
It is also possible to retrieve the whole data
object by using getActivatedRouteConfig
:
export class AppComponent {
data$ = this.routeConfigService.getActivatedRouteConfig();
dataWithDefaultValue$ = this.routeConfigService.getActivatedRouteConfig({
routeTags: ['defaultTag'],
title: 'Default Title',
});
}
And if you want to use your custom data properties you can create your custom types:
export type AppRouteConfigParams = 'title';
export type AppRouteTag = keyof typeof AppRouteTags;
export enum AppRouteTags {
show = 'show',
}
and provide them when injecting RouteConfigService
export class AppComponent {
title$ = this.routeConfigService.getLeafConfig('title', 'Default Title');
constructor(private routeConfigService: RouteConfigService<AppRouteTag, AppRouteConfigParams>) {}
}
In this case your example router config can look like this:
@NgModule({
declarations: [FirstRouteComponent, SecondRouteComponent],
imports: [
RouterModule.forRoot([
{
path: 'first',
component: FirstRouteComponent,
data: {
routeTags: [AppRouteTags.show], // use enum to get more type safety
},
},
{
path: 'second',
component: SecondRouteComponent,
data: {
title: 'Second Route Title',
},
},
]),
],
exports: [RouterModule],
})
export class AppModule {}
Both *tdRouteData
and RouteConfigService
allow for providing a default value in case route data doesn't provide a certain property. It is also possible to provide a default value globally by providing ROUTE_DATA_DEFAULT_VALUE
injection token when configuring the root module:
@NgModule({
/* other module props */
imports: [RouteConfigModule.forRoot() /* other modules */],
provide: [
{
provide: ROUTE_DATA_DEFAULT_VALUE,
useValue: {
title: 'Injected Default Title',
someDefaultParam: 'Some other default param',
},
},
],
})
export class AppModule {}
This way we don't need to provide default value each time we use *tdRouteData
or RouteConfigService
. However, if a different default value is necessary it still can be provided - and it will overwrite the value injected with a token.
The injection token can also be provided / overridden in a component's declaration:
@Component({
// ...
providers: [ /* the value provided here will be the default down in the DOM tree */]
})
The inRouteTags$
pipe takes an array of route tags and returns an Observable. This observable emits a true value if any of the values in the provided array is in the configured route tags of the activated route.
import { Component } from '@angular/core';
@Component({
selector: 'custom-component',
templateUrl: './custom-component.html',
})
export class CustomComponent {
displayRouteTags = ['custom-component', 'another-tag', 'third-tag'];
}
<!-- custom-component.html -->
<ng-container *ngIf="displayRouteTags | inRouteTags$ | async">
The contents of this ng-container are only displayed when the activated route's route config has
the above declared route tags.
</ng-container>