@ngrdt/router
v0.0.80
Published
Main use for this package is to define routes and their parameters in one place in app. It is recommended to define special `routes` module in every app using `@ngrdt/router` where all `RdtRoute` objects are defined using `RdtRouteBuilder`. Individual app
Readme
@ngrdt/router
Main use for this package is to define routes and their parameters in one place in app. It is recommended to define special routes module in every app using @ngrdt/router where all RdtRoute objects are defined using RdtRouteBuilder. Individual application modules then import these objects, define Angular routes and provide implementation details such as which component is rendered, state, guards, etc.
This approach ensures that each route is defined exactly once and can be easily edited if necessary. @ngrdt/router also provides set of useful tools to help you with common routing tasks.
Instalation
npm install @ngrdt/router
Basic usage
Application routes file
Create special nx library (not NgModule) for instance @example-app/routes. In this library you will create file called routes.ts and in it individual RdtRoute objects like so:
export const DASHBOARD_ITEM_ROUTE = new RdtRouteBuilder()
.setName('Dashboard item')
.setPath(':id')
.setParam('id', 'number')
.build();
export const DASHBOARD_ROUTE = new RdtRouteBuilder()
.setName('Dashboard')
.setPath('dashboard')
.setChildren(DASHBOARD_ITEM_ROUTE)
.build();
export const SETTINGS_ROUTE = new RdtRouteBuilder()
.setName('Settings')
.setPath('settings')
.setCanBeEntered(() =>
inject(ExampleAppPermissionService).hasPermission('SETTINGS_ACCESS')
)
.build();This file should only contain imports to interfaces used by RdtRouteBuilder as template parameter. No component, ngModule or guards such as CanActivateFn should be imported here. This approach will save you later headache with circular dependency.
RdtRouterService needs to be aware of all routes defined therefore you must provide it in your ApplicationConfig like so:
import { RDT_ROUTES_PROVIDER, RdtRoute } from '@ngrdt/router';
import * as ALL_ROUTES_OBJ from './rdt-routes';
const ALL_ROUTES = Object.values(ALL_ROUTES_OBJ) as RdtRoute[];
export const appConfig: ApplicationConfig = {
providers: [
{
provide: RDT_ROUTES_PROVIDER,
useValue: ALL_ROUTES,
},
]
}Angular routes files
Now you have to bind RdtRoute objects to individual modules, components, states, effects, guards and other implementation details. Instead of defining routes directly you import RdtRoute from @example-app/routes and then define routes.ts like so:
export const routes: Route[] = [
DASHBOARD_ROUTE.toAngularRoute()
.setComponent(DashboardComponent)
.setChildren(
DASHBOARD_ITEM_ROUTE.toAngularRoute()
.setComponent(DashboardItemComponent)
.addCanDeactivate(preventDataLossGuardFn)
.build()
)
.build(),
]This array is then used as in regular Angular project. Note that child routes must be added as children here as well.
Child routes and parameters
You may nest child routes and also create routes with typed parameters to link them with particular type.
For instance lets say there's a route displaying users which has a child route to display more information about a single user. By providing User type to RdtRouteBuilder and defining types of parameters we can use RdtRoute object to parse path to extract parameters.
interface User {
userId: number;
name: string;
}
export const USER_DETAIL_ROUTE = new RdtRouteBuilder<User>()
.setName('User detail')
.setPath(':userId/:name')
.setParam('userId', 'number')
.setParam('name', 'string')
.build();
export const USER_LIST_ROUTE = new RdtRouteBuilder()
.setName('Users')
.setPath('users')
.setChildren(USER_DETAIL_ROUTE)
.build();Then we navigate to user detail like so:
const params: Partial<User> = {
userId: 123,
name: 'Josef',
};
inject(RdtRouterService).navigate(USER_DETAIL_ROUTE, params);RdtRouterService is aware of the type so you can use it to parse absolute path into User object. Route knows userId is number so it will parse it. If parameter is missing or is of wrong type, then parseAbsoluteUrl returns null. Return value is a wrapper object with get(route: RdtRoute) method to get parsed parameters of given route or its parents.
const user: User = USER_DETAIL_ROUTE
.parse('/users/123/Josef')
.get(USER_DETAIL_ROUTE);More parameter examples
Static parameters
Imagine your app contains list of users and each user has set of tasks. The routes are defined as follows:
interface User {
userId: number,
name: string,
address: string,
tasks: Task[]
}
interface Task {
taskId: number,
description: string
}
export const TASK_DETAIL_ROUTE = new RdtRouteBuilder<Task>()
.setName('Task detail')
.setPath('task/:taskId')
.setParam('taskId', 'number')
.build();
export const USER_DETAIL_ROUTE = new RdtRouteBuilder<User>()
.setName('User detail')
.setPath(':userId')
.setParam('userId', 'number')
.setChildren(TASK_DETAIL_ROUTE)
.build();
export const USER_LIST_ROUTE = new RdtRouteBuilder()
.setName('Users')
.setPath('user')
.setChildren(USER_DETAIL_ROUTE)
.build();This translates into path /user/:userId/task/:taskId for task detail route.
Now inside UserDetailComponent there's a for loop rendering individual tasks and you want to have links to them. You cannot use directly:
<button [rdtRouterLink]="TASK_DETAIL_ROUTE" [params]="task"></button>Because task does not specify userId. For this you must provide static parameters for every parent route - User for USER_DETAIL_ROUTE in this case:
@Input()
user!: User;
taskRoute!: RdtRoute<Task>;
ngOnInit() {
this.taskRoute = TASK_DETAIL_ROUTE.withStaticParams(USER_DETAIL_ROUTE, this.user);
}Method withStaticParams() returns clone of TASK_DETAIL_ROUTE and all of its parent routes while fixing parameters. You can chain calling withStaticParams with itself or other method calls.
<button [rdtRouterLink]="taskRoute" [params]="task"></button>Ecosystem
RdtRouterLink
RdtRouterLink is equivalent of RouterLink in Angular. It supports basically the same functionality, but adds type safety. RdtRouterLink will be disabled if canBeEntered of its route returns false.
readonly userRoute = USER_DETAIL_ROUTE;
readonly user$ = inject(UserService).getUser$();<button
[rdtRouterLink]="userRoute"
[params]="user$ | async"
target="_blank">
Link to user
</button>RdtMenu
RdtRoute can be passed directly into RdtMenuItem. If canBeEntered returns false, then the item won't be visible. This is useful to hide items user has no permission for.
Method withStaticParams lets us provide parameters for given RdtRoute, because RdtMenuItem does not accept route parameters.
public getMenuItems(): RdtMenuItem[] {
const user: Partial<User> = {
userId: 123,
name: 'Josef'
};
return [
{
label: 'All users',
routerLink: USER_LIST_ROUTE
},
{
label: 'Josef',
routerLink: USER_DETAIL_ROUTE.withStaticParams(user)
}
];
}RdtAngularRoute
RdtAngularRoute is a container class for RdtRoute which functionally does two things: verifies that routes were defined correctly and enables you to provide guards, effects, component or module. Don't forget to call build() for it to generate Angular Route.
RdtRouterService
RdtRouterService has similar features as Angular Router.
parseAbsoluteUrl()
Method will parse absolute path, recognize RdtRoute and parse its parameters. If no parameter is provided, then window location is used.
navigateBack()
Will push parent route to navigation stack. Function works anywhere in the app unlike similar Angular-based solution using ActivatedRoute.
Hitting native browser back button will take you back forward. In case you want true back function, use inject(Location).back() which removes from navigation stack, but can potentially take you outside of the app or close browser window.
previousUrl and currentUrl
Cached sync values of previous and current path values. For example /dashboard or /user/123/task/xyz.
