npm package: @ngrdt/router
Description: 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
Latest version: 0.0.99

# @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:

```typescript
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:

```typescript
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:

```typescript
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.

```typescript
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:

```typescript
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.


```typescript
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:

```typescript
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:

```html
<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:

```typescript
@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.

```html
<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`.

```typescript
readonly userRoute = USER_DETAIL_ROUTE;
readonly user$ = inject(UserService).getUser$();
```

```html
<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.

```typescript
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`.