angular-typed-router 0.1.1-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -0
- package/fesm2022/angular-typed-router.mjs +46 -0
- package/fesm2022/angular-typed-router.mjs.map +1 -0
- package/index.d.ts +66 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# angular-typed-router
|
|
2
|
+
|
|
3
|
+
## Attention: Still in experimental phase
|
|
4
|
+
|
|
5
|
+
Type-safe ergonomic primitives on top of Angular's standalone router. Automatically derive a compile‑time union of valid route URL strings (`Path`) and strongly typed navigation command tuples (`Commands`) from the consumer application's `Routes` definition – without generating code or adding runtime weight.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Angular's router is powerful but untyped for URL literals – a misspelled path or an outdated segment only fails at runtime. This library lets your application declare routes once, then:
|
|
10
|
+
- Navigate with `TypedRouter.navigateByUrl(path)` where `path` is validated at compile time.
|
|
11
|
+
- Use `<a routerLink="...">` with type checking via an augmented `TypedRouterLink` directive.
|
|
12
|
+
- Build command tuples with correct literal segments (`Commands` type).
|
|
13
|
+
|
|
14
|
+
No decorators, no custom builders, no code generation – just TypeScript type inference and interface augmentation.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install angular-typed-router
|
|
20
|
+
# or
|
|
21
|
+
pnpm add angular-typed-router
|
|
22
|
+
# or
|
|
23
|
+
yarn add angular-typed-router
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
1. Define your application routes:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// app.routes.ts
|
|
32
|
+
import { Routes } from '@angular/router';
|
|
33
|
+
import { DashboardComponent } from './dashboard.component';
|
|
34
|
+
|
|
35
|
+
export const appRoutes = [
|
|
36
|
+
{ path: 'dashboard', component: DashboardComponent },
|
|
37
|
+
{ path: 'projects/:id', loadComponent: () => import('./project.component').then(m => m.ProjectComponent) },
|
|
38
|
+
{ path: '**', redirectTo: 'dashboard' }
|
|
39
|
+
] as const satisfies Routes;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
2. Create the augmentation file so the library can “see” your routes:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// angular-typed-router.d.ts (sibling to main.ts or inside src/ root)
|
|
46
|
+
import type { appRoutes } from './app/app.routes';
|
|
47
|
+
|
|
48
|
+
declare module 'angular-typed-router' {
|
|
49
|
+
interface UserTypedRoutes {
|
|
50
|
+
routes: typeof appRoutes;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
3. Link the augmentation file in your tsconfig so the compiler includes it:
|
|
56
|
+
|
|
57
|
+
```jsonc
|
|
58
|
+
// tsconfig.app.json
|
|
59
|
+
{
|
|
60
|
+
"extends": "./tsconfig.json",
|
|
61
|
+
"compilerOptions": { },
|
|
62
|
+
"include": [
|
|
63
|
+
"src/**/*.ts",
|
|
64
|
+
"angular-typed-router.d.ts" // <— add this line
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
If you have multiple tsconfigs, ensure the specific app tsconfig that drives the build/test includes the file.
|
|
69
|
+
|
|
70
|
+
4. Use the typed router & link:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { Component, inject } from '@angular/core';
|
|
74
|
+
import { TypedRouter, Path } from 'angular-typed-router';
|
|
75
|
+
|
|
76
|
+
@Component({
|
|
77
|
+
selector: 'app-nav',
|
|
78
|
+
template: `
|
|
79
|
+
<a routerLink="dashboard">Dashboard</a>
|
|
80
|
+
<a routerLink="projects/123">Project 123</a>
|
|
81
|
+
`
|
|
82
|
+
})
|
|
83
|
+
export class NavComponent {
|
|
84
|
+
private readonly router = inject(TypedRouter);
|
|
85
|
+
|
|
86
|
+
go(p: Path) { // p must be one of the inferred paths
|
|
87
|
+
this.router.navigateByUrl(p);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
openProject(id: string) {
|
|
91
|
+
// commands tuple – first literal segment + dynamic param value
|
|
92
|
+
this.router.navigate(['projects', id]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If you try `this.router.navigateByUrl('projcts/123')` (typo) or `<a routerLink="projcts/123">`, TypeScript errors.
|
|
98
|
+
|
|
99
|
+
## Exports
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { TypedRouter, TypedRouterLink, Path, Commands, UserTypedRoutes } from 'angular-typed-router';
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- `TypedRouter` – Extends Angular `Router`, overrides `navigateByUrl` & `navigate` signatures to accept `Path` / `Commands`.
|
|
106
|
+
- `TypedRouterLink` – Directive shadowing `[routerLink]` to type its input as `Commands | Path`.
|
|
107
|
+
- `Path` – Union of every reachable concrete URL path produced from your route tree (includes parameterized expansions with `string` in place of `:param` segments).
|
|
108
|
+
- `Commands` – Union of tuple command arrays representing valid `Router.navigate()` inputs (each static segment as a literal, each parameter position as `string`).
|
|
109
|
+
- `UserTypedRoutes` – Empty interface you augment with your `routes` reference.
|
|
110
|
+
- `ExtractPathsFromRoutes<Routes>` – Utility type if you need to compute from an arbitrary `Routes` array manually.
|
|
111
|
+
|
|
112
|
+
## How It Works
|
|
113
|
+
|
|
114
|
+
1. You augment `UserTypedRoutes` with the literal `const` route array.
|
|
115
|
+
2. Type utilities recursively walk the route tree (including lazily loaded routes via `loadChildren` returning `Route[]` or `{ routes }`).
|
|
116
|
+
3. Each navigable route (component / loadComponent / redirectTo) contributes a path string. Param segments (`:id`) are replaced by `string` (currently unrestricted – see Limitations).
|
|
117
|
+
4. Child paths are joined with parents to form final concrete path unions.
|
|
118
|
+
5. A tuple transformation creates the `Commands` variants.
|
|
119
|
+
|
|
120
|
+
All compile-time only; nothing ships to runtime.
|
|
121
|
+
|
|
122
|
+
## Lazy Routes
|
|
123
|
+
|
|
124
|
+
Works with any lazy route whose `loadChildren` resolves to:
|
|
125
|
+
- `Promise<Route[]>`
|
|
126
|
+
- `Promise<{ routes: Route[] }>` (Angular v17+ pattern)
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
{ path: 'admin', loadChildren: () => import('./admin.routes').then(m => m.adminRoutes) }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Those child paths get prefixed (`admin/...`) in `Path` & `Commands`.
|
|
135
|
+
|
|
136
|
+
## Parameter Segments
|
|
137
|
+
|
|
138
|
+
A pattern `projects/:id/details/:section` produces a `Path` variant like:
|
|
139
|
+
```
|
|
140
|
+
'projects/' + string + '/details/' + string
|
|
141
|
+
```
|
|
142
|
+
and a `Commands` tuple like:
|
|
143
|
+
```
|
|
144
|
+
['projects', string, 'details', string]
|
|
145
|
+
```
|
|
146
|
+
You pass real runtime values for the `string` positions. Empty string values and values like 'param/still-param' cannot currently be prevented at the type level without hurting DX (see Limitations).
|
|
147
|
+
|
|
148
|
+
## Usage Patterns
|
|
149
|
+
|
|
150
|
+
Navigate by full path (typed):
|
|
151
|
+
```ts
|
|
152
|
+
router.navigateByUrl('/dashboard');
|
|
153
|
+
```
|
|
154
|
+
Navigate with commands array:
|
|
155
|
+
```ts
|
|
156
|
+
router.navigate(['/', 'projects', someId]);
|
|
157
|
+
```
|
|
158
|
+
Generate a `UrlTree`:
|
|
159
|
+
```ts
|
|
160
|
+
router.createUrlTree(['/', 'projects', id]);
|
|
161
|
+
```
|
|
162
|
+
Template links:
|
|
163
|
+
```html
|
|
164
|
+
<a routerLink="/projects/42">Project 42</a>
|
|
165
|
+
<a [routerLink]="['/', 'projects', projectId]"></a>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
## Augmentation Placement
|
|
170
|
+
|
|
171
|
+
Keep the augmentation in a `.d.ts` that is included by `tsconfig.app.json` (`include` array). If you see `Path` still as `never`, ensure:
|
|
172
|
+
- The augmentation file is included.
|
|
173
|
+
- The `routes` constant is `as const satisfies Routes`.
|
|
174
|
+
- No circular import (augmentation file should only import the routes, nothing else runtime-heavy).
|
|
175
|
+
|
|
176
|
+
## Limitations & Tradeoffs
|
|
177
|
+
|
|
178
|
+
| Concern | Status / Rationale |
|
|
179
|
+
|---------|---------------------------------------------------------------------------------------|
|
|
180
|
+
| Restrict param values (non-empty) | Not enforced; doing so significantly worsens DX (would reject plain `string` vars). |
|
|
181
|
+
| Trailing slashes | Not generated unless authored; currently no auto-normalization. |
|
|
182
|
+
| `relativeTo` (relative navigation) | Not supported – all inferred `Path` / `Commands` are absolute. Use absolute commands. |
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
## ESLint Recommendation (Optional)
|
|
186
|
+
|
|
187
|
+
You can use `angular-typed-router-eslint` plugin to forbid untyped navigation calls.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
|
|
191
|
+
## Troubleshooting
|
|
192
|
+
|
|
193
|
+
| Symptom | Fix |
|
|
194
|
+
|---------|-----|
|
|
195
|
+
| `Path` is `never` | Check augmentation file is included in tsconfig. |
|
|
196
|
+
| Lazy children missing | Ensure promise resolves to `Route[]` or `{ routes: Route[] }`. |
|
|
197
|
+
| Template error: unknown routerLink type | Ensure `TypedRouterLink` directive is imported (standalone). |
|
|
198
|
+
|
|
199
|
+
## Contributing
|
|
200
|
+
|
|
201
|
+
PRs welcome.
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
Happy routing.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injectable, Input, Directive } from '@angular/core';
|
|
3
|
+
import { Router, RouterLink } from '@angular/router';
|
|
4
|
+
|
|
5
|
+
class TypedRouter extends Router {
|
|
6
|
+
navigate(commands, extras) {
|
|
7
|
+
return super.navigate(commands, extras);
|
|
8
|
+
}
|
|
9
|
+
navigateByUrl(url, extras) {
|
|
10
|
+
return super.navigateByUrl(url, extras);
|
|
11
|
+
}
|
|
12
|
+
createUrlTree(commands, navigationExtras) {
|
|
13
|
+
return super.createUrlTree(commands, navigationExtras);
|
|
14
|
+
}
|
|
15
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouter, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
16
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouter, providedIn: 'root' });
|
|
17
|
+
}
|
|
18
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouter, decorators: [{
|
|
19
|
+
type: Injectable,
|
|
20
|
+
args: [{
|
|
21
|
+
providedIn: 'root',
|
|
22
|
+
}]
|
|
23
|
+
}] });
|
|
24
|
+
|
|
25
|
+
class TypedRouterLink extends RouterLink {
|
|
26
|
+
set routerLink(commandsOrUrlTree) {
|
|
27
|
+
super.routerLink = commandsOrUrlTree;
|
|
28
|
+
}
|
|
29
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouterLink, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
30
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.2.4", type: TypedRouterLink, isStandalone: true, selector: "[routerLink]", inputs: { routerLink: "routerLink" }, usesInheritance: true, ngImport: i0 });
|
|
31
|
+
}
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouterLink, decorators: [{
|
|
33
|
+
type: Directive,
|
|
34
|
+
args: [{
|
|
35
|
+
selector: '[routerLink]'
|
|
36
|
+
}]
|
|
37
|
+
}], propDecorators: { routerLink: [{
|
|
38
|
+
type: Input
|
|
39
|
+
}] } });
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generated bundle index. Do not edit.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
export { TypedRouter, TypedRouterLink };
|
|
46
|
+
//# sourceMappingURL=angular-typed-router.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angular-typed-router.mjs","sources":["../tmp-esm2022/lib/router.js","../tmp-esm2022/lib/router-link.js","../tmp-esm2022/angular-typed-router.js"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { Router, } from '@angular/router';\nimport * as i0 from \"@angular/core\";\nexport class TypedRouter extends Router {\n navigate(commands, extras) {\n return super.navigate(commands, extras);\n }\n navigateByUrl(url, extras) {\n return super.navigateByUrl(url, extras);\n }\n createUrlTree(commands, navigationExtras) {\n return super.createUrlTree(commands, navigationExtras);\n }\n static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouter, deps: null, target: i0.ɵɵFactoryTarget.Injectable });\n static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouter, providedIn: 'root' });\n}\ni0.ɵɵngDeclareClassMetadata({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouter, decorators: [{\n type: Injectable,\n args: [{\n providedIn: 'root',\n }]\n }] });\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vbGlicy90eXBlZC1yb3V0ZXIvc3JjL2xpYi9yb3V0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBR0wsTUFBTSxHQUdQLE1BQU0saUJBQWlCLENBQUM7O0FBTXpCLE1BQU0sT0FBTyxXQUFZLFNBQVEsTUFBTTtJQUM1QixRQUFRLENBQ2YsUUFBa0IsRUFDbEIsTUFBeUI7UUFFekIsT0FBTyxLQUFLLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRVEsYUFBYSxDQUNwQixHQUFTLEVBQ1QsTUFBa0M7UUFFbEMsT0FBTyxLQUFLLENBQUMsYUFBYSxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRVEsYUFBYSxDQUNwQixRQUFrQixFQUNsQixnQkFBcUM7UUFFckMsT0FBTyxLQUFLLENBQUMsYUFBYSxDQUFDLFFBQVEsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3pELENBQUM7dUdBcEJVLFdBQVc7MkdBQVgsV0FBVyxjQUZWLE1BQU07OzJGQUVQLFdBQVc7a0JBSHZCLFVBQVU7bUJBQUM7b0JBQ1YsVUFBVSxFQUFFLE1BQU07aUJBQ25CIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHtcbiAgTmF2aWdhdGlvbkJlaGF2aW9yT3B0aW9ucyxcbiAgTmF2aWdhdGlvbkV4dHJhcyxcbiAgUm91dGVyLFxuICBVcmxDcmVhdGlvbk9wdGlvbnMsXG4gIFVybFRyZWUsXG59IGZyb20gJ0Bhbmd1bGFyL3JvdXRlcic7XG5pbXBvcnQgeyBDb21tYW5kcywgUGF0aCB9IGZyb20gJy4vdHlwZWQtcm91dGVzJztcblxuQEluamVjdGFibGUoe1xuICBwcm92aWRlZEluOiAncm9vdCcsXG59KVxuZXhwb3J0IGNsYXNzIFR5cGVkUm91dGVyIGV4dGVuZHMgUm91dGVyIHtcbiAgb3ZlcnJpZGUgbmF2aWdhdGUoXG4gICAgY29tbWFuZHM6IENvbW1hbmRzLFxuICAgIGV4dHJhcz86IE5hdmlnYXRpb25FeHRyYXNcbiAgKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgcmV0dXJuIHN1cGVyLm5hdmlnYXRlKGNvbW1hbmRzLCBleHRyYXMpO1xuICB9XG5cbiAgb3ZlcnJpZGUgbmF2aWdhdGVCeVVybChcbiAgICB1cmw6IFBhdGgsXG4gICAgZXh0cmFzPzogTmF2aWdhdGlvbkJlaGF2aW9yT3B0aW9uc1xuICApOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICByZXR1cm4gc3VwZXIubmF2aWdhdGVCeVVybCh1cmwsIGV4dHJhcyk7XG4gIH1cblxuICBvdmVycmlkZSBjcmVhdGVVcmxUcmVlKFxuICAgIGNvbW1hbmRzOiBDb21tYW5kcyxcbiAgICBuYXZpZ2F0aW9uRXh0cmFzPzogVXJsQ3JlYXRpb25PcHRpb25zXG4gICk6IFVybFRyZWUge1xuICAgIHJldHVybiBzdXBlci5jcmVhdGVVcmxUcmVlKGNvbW1hbmRzLCBuYXZpZ2F0aW9uRXh0cmFzKTtcbiAgfVxufVxuIl19","import { Directive, Input } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport * as i0 from \"@angular/core\";\nexport class TypedRouterLink extends RouterLink {\n set routerLink(commandsOrUrlTree) {\n super.routerLink = commandsOrUrlTree;\n }\n static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouterLink, deps: null, target: i0.ɵɵFactoryTarget.Directive });\n static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: \"14.0.0\", version: \"20.2.4\", type: TypedRouterLink, isStandalone: true, selector: \"[routerLink]\", inputs: { routerLink: \"routerLink\" }, usesInheritance: true, ngImport: i0 });\n}\ni0.ɵɵngDeclareClassMetadata({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouterLink, decorators: [{\n type: Directive,\n args: [{\n selector: '[routerLink]'\n }]\n }], propDecorators: { routerLink: [{\n type: Input\n }] } });\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLWxpbmsuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9saWJzL3R5cGVkLXJvdXRlci9zcmMvbGliL3JvdXRlci1saW5rLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2pELE9BQU8sRUFBRSxVQUFVLEVBQVcsTUFBTSxpQkFBaUIsQ0FBQzs7QUFNdEQsTUFBTSxPQUFPLGVBQWdCLFNBQVEsVUFBVTtJQUM3QyxJQUNhLFVBQVUsQ0FBQyxpQkFBK0Q7UUFDckYsS0FBSyxDQUFDLFVBQVUsR0FBRyxpQkFBaUIsQ0FBQztJQUN2QyxDQUFDO3VHQUpVLGVBQWU7MkZBQWYsZUFBZTs7MkZBQWYsZUFBZTtrQkFIM0IsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsY0FBYztpQkFDekI7OEJBR2MsVUFBVTtzQkFEdEIsS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IERpcmVjdGl2ZSwgSW5wdXQgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFJvdXRlckxpbmssIFVybFRyZWUgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xuaW1wb3J0IHsgQ29tbWFuZHMsIFBhdGggfSBmcm9tICcuL3R5cGVkLXJvdXRlcyc7XG5cbkBEaXJlY3RpdmUoe1xuICBzZWxlY3RvcjogJ1tyb3V0ZXJMaW5rXSdcbn0pXG5leHBvcnQgY2xhc3MgVHlwZWRSb3V0ZXJMaW5rIGV4dGVuZHMgUm91dGVyTGluayB7XG4gIEBJbnB1dCgpXG4gIG92ZXJyaWRlIHNldCByb3V0ZXJMaW5rKGNvbW1hbmRzT3JVcmxUcmVlOiBDb21tYW5kcyB8IFBhdGggfCBVcmxUcmVlIHwgbnVsbCB8IHVuZGVmaW5lZCkge1xuICAgIHN1cGVyLnJvdXRlckxpbmsgPSBjb21tYW5kc09yVXJsVHJlZTtcbiAgfVxufVxuIl19","/**\n * Generated bundle index. Do not edit.\n */\nexport * from './index';\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYW5ndWxhci10eXBlZC1yb3V0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9saWJzL3R5cGVkLXJvdXRlci9zcmMvYW5ndWxhci10eXBlZC1yb3V0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0="],"names":[],"mappings":";;;;AAGO,MAAM,WAAW,SAAS,MAAM,CAAC;AACxC,IAAI,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE;AAC/B,QAAQ,OAAO,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;AAC/C,IAAI;AACJ,IAAI,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE;AAC/B,QAAQ,OAAO,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC;AAC/C,IAAI;AACJ,IAAI,aAAa,CAAC,QAAQ,EAAE,gBAAgB,EAAE;AAC9C,QAAQ,OAAO,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,gBAAgB,CAAC;AAC9D,IAAI;AACJ,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC;AACxK,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAC7I;AACA,EAAE,CAAC,wBAAwB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AACrH,YAAY,IAAI,EAAE,UAAU;AAC5B,YAAY,IAAI,EAAE,CAAC;AACnB,oBAAoB,UAAU,EAAE,MAAM;AACtC,iBAAiB;AACjB,SAAS,CAAC,EAAE,CAAC;;AClBN,MAAM,eAAe,SAAS,UAAU,CAAC;AAChD,IAAI,IAAI,UAAU,CAAC,iBAAiB,EAAE;AACtC,QAAQ,KAAK,CAAC,UAAU,GAAG,iBAAiB;AAC5C,IAAI;AACJ,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,EAAE,CAAC;AAC3K,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC,oBAAoB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AACtO;AACA,EAAE,CAAC,wBAAwB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;AACzH,YAAY,IAAI,EAAE,SAAS;AAC3B,YAAY,IAAI,EAAE,CAAC;AACnB,oBAAoB,QAAQ,EAAE;AAC9B,iBAAiB;AACjB,SAAS,CAAC,EAAE,cAAc,EAAE,EAAE,UAAU,EAAE,CAAC;AAC3C,gBAAgB,IAAI,EAAE;AACtB,aAAa,CAAC,EAAE,EAAE,CAAC;;ACjBnB;AACA;AACA;;;;"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Route, Router, NavigationExtras, NavigationBehaviorOptions, UrlCreationOptions, UrlTree, RouterLink } from '@angular/router';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
|
|
4
|
+
type _Normalize<S extends string> = string extends S ? string : S;
|
|
5
|
+
type PathToTuple<P extends string> = P extends `${infer Head}/${infer Rest}` ? [_Normalize<Head>, ...PathToTuple<Rest>] : P extends '' ? [] : [_Normalize<P>];
|
|
6
|
+
|
|
7
|
+
type IsNavigable<R extends Route> = R extends {
|
|
8
|
+
component: any;
|
|
9
|
+
} ? true : R extends {
|
|
10
|
+
loadComponent: any;
|
|
11
|
+
} ? true : R extends {
|
|
12
|
+
redirectTo: any;
|
|
13
|
+
} ? true : false;
|
|
14
|
+
|
|
15
|
+
declare const __rootCatchAll: unique symbol;
|
|
16
|
+
type RootCatchAll = string & {
|
|
17
|
+
readonly [__rootCatchAll]: true;
|
|
18
|
+
};
|
|
19
|
+
type AllowedRouteParamValue<S extends string> = Exclude<S, '' | `${string}/${string}`>;
|
|
20
|
+
type _ReplaceParams<S extends string> = S extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${AllowedRouteParamValue<string>}/${_ReplaceParams<Rest>}` : S extends `${infer Start}:${string}` ? `${Start}${AllowedRouteParamValue<string>}` : S extends `${infer Start}**/${infer Rest}` ? `${Start}${string}/${_ReplaceParams<Rest>}` : S extends `${infer Start}**` ? Start extends '' ? RootCatchAll : `${Start}${string}` : S;
|
|
21
|
+
type ReplaceParams<S extends string> = _ReplaceParams<S>;
|
|
22
|
+
|
|
23
|
+
type PathOrEmptyString<R extends Route> = R['path'] extends string ? R['path'] : '';
|
|
24
|
+
|
|
25
|
+
type ExtractLazyChildRoutes<R extends Route> = R['loadChildren'] extends (...args: any) => Promise<infer M> ? M extends readonly Route[] ? M : M extends {
|
|
26
|
+
routes: readonly Route[];
|
|
27
|
+
} ? M['routes'] : [] : [];
|
|
28
|
+
|
|
29
|
+
type JoinPathSegments<A extends string, B extends string> = A extends '' ? B : B extends '' ? A : `${A}/${B}`;
|
|
30
|
+
|
|
31
|
+
type ExtractChildren<Routes extends readonly Route[], Prefix extends string> = Routes[number] extends infer R ? R extends Route ? ExtractPathsFromRoute<R, Prefix> : never : never;
|
|
32
|
+
type ExtractPathsFromRoute<R extends Route, Prefix extends string = ''> = (IsNavigable<R> extends true ? ReplaceParams<JoinPathSegments<Prefix, PathOrEmptyString<R>>> : never) | (R['children'] extends readonly Route[] ? ExtractChildren<R['children'], ReplaceParams<JoinPathSegments<Prefix, PathOrEmptyString<R>>>> : never) | (R['loadChildren'] extends () => Promise<any> ? ExtractChildren<ExtractLazyChildRoutes<R>, ReplaceParams<JoinPathSegments<Prefix, PathOrEmptyString<R>>>> : never);
|
|
33
|
+
|
|
34
|
+
type ExtractPathsFromRoutes<Routes extends readonly Route[], Prefix extends string = ''> = Routes[number] extends infer R ? R extends Route ? ExtractPathsFromRoute<R, Prefix> : never : never;
|
|
35
|
+
|
|
36
|
+
type RemoveTrailingSlash<S extends string> = S extends '/' ? S : S extends `${infer T}/` ? RemoveTrailingSlash<T> : S;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Merge target. Consumer augments this interface:
|
|
40
|
+
* declare module 'angular-typed-router' { interface UserTypedRoutes { routes: typeof routes; } }`
|
|
41
|
+
*/
|
|
42
|
+
interface UserTypedRoutes {
|
|
43
|
+
}
|
|
44
|
+
type ProvidedRoutes = UserTypedRoutes extends {
|
|
45
|
+
routes: readonly Route[];
|
|
46
|
+
} ? UserTypedRoutes['routes'] : [];
|
|
47
|
+
type Path = RemoveTrailingSlash<`/${ExtractPathsFromRoutes<ProvidedRoutes>}`>;
|
|
48
|
+
type CommandPath = ExtractPathsFromRoutes<ProvidedRoutes>;
|
|
49
|
+
type Commands = ['/', ...PathToTuple<CommandPath>];
|
|
50
|
+
|
|
51
|
+
declare class TypedRouter extends Router {
|
|
52
|
+
navigate(commands: Commands, extras?: NavigationExtras): Promise<boolean>;
|
|
53
|
+
navigateByUrl(url: Path, extras?: NavigationBehaviorOptions): Promise<boolean>;
|
|
54
|
+
createUrlTree(commands: Commands, navigationExtras?: UrlCreationOptions): UrlTree;
|
|
55
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouter, never>;
|
|
56
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<TypedRouter>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
declare class TypedRouterLink extends RouterLink {
|
|
60
|
+
set routerLink(commandsOrUrlTree: Commands | Path | UrlTree | null | undefined);
|
|
61
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouterLink, never>;
|
|
62
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<TypedRouterLink, "[routerLink]", never, { "routerLink": { "alias": "routerLink"; "required": false; }; }, {}, never, never, true, never>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { TypedRouter, TypedRouterLink };
|
|
66
|
+
export type { Commands, ExtractPathsFromRoutes, Path, UserTypedRoutes };
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "angular-typed-router",
|
|
3
|
+
"version": "0.1.1-0",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@angular/common": "^20.2.0",
|
|
6
|
+
"@angular/core": "^20.2.0"
|
|
7
|
+
},
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"module": "fesm2022/angular-typed-router.mjs",
|
|
10
|
+
"typings": "index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
"./package.json": {
|
|
13
|
+
"default": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./index.d.ts",
|
|
17
|
+
"default": "./fesm2022/angular-typed-router.mjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"tslib": "^2.3.0"
|
|
22
|
+
}
|
|
23
|
+
}
|