ui-router-registry
ui-router-registry types the state names and params of a @uirouter/core router from the app's own as const state declarations. It is types only and emits no JavaScript.
Installation
npm install --save-dev ui-router-registry@uirouter/core is a peer dependency, for its types only. Install one copy of this package per app: two copies mean two Registers, and the augmentation types only one.
Registering states
Register the app's declarations once:
import type { states } from './states';
declare module 'ui-router-registry' {
interface Register {
states: (typeof states)[number];
}
}Until an app registers, every type falls back to core's string and RawParams, so existing calls compile unchanged.
Typed calls
Annotate where the router is used. Core's StateService is assignable to TypedStateService, so the annotation needs no cast:
import type { TypedStateService } from 'ui-router-registry';
const stateService: TypedStateService = router.stateService;
stateService.go('contacts.contact', { contactId: 'c1' });| Type | What it holds |
|---|---|
Register | the interface an app augments with its as const state declarations |
StateName | the registered state names; dotted names and parent: both resolve, and a future state's name.** placeholder is left out |
ParamsOf and ResolvedParamsOf | the params a state accepts and the params it reads back, its ancestors' included; url placeholders are required, query params are not |
TypedStateService | go, transitionTo, target, href, is and includes over registered names; relative refs are checked by their tail, and object targets stay loose |
TypedRouteSnapshot | the route snapshot the reactive packages share, whose includes(name) narrows current and params to that state's subtree |
Relative refs and unchecked targets
A relative ref (.child, ^, ^.sibling) resolves against the uiSref's context or options.relative, which types can't see. Its tail is checked against the registered names, so .contcat fails, and its params stay loose. A relative ref also lets a link reuse the current params instead of restating them: uiSref('.edit') from contacts.contact inherits contactId.
A target the registry can't see, such as a name built at runtime or a state registered at runtime, is a string and fails once an app registers. Opt out explicitly with UncheckedRef; its params are loose:
import type { UncheckedRef } from 'ui-router-registry';
uiSref(link.state as UncheckedRef, link.params);
stateService.go(name as UncheckedRef);Annotate members that call typed targets
A state declaration that names a component and is checked with satisfies against a type that inspects the component, such as LitStateDeclaration, makes that component part of Register's type. If one of the component's members calls a typed target (go, href) and has no declared return type, TypeScript has to infer that member from Register while still building Register, and reports a circular type (TS7022, TS7023, TS2456). Declare the member's return type:
render(): TemplateResult {
return html`<a href=${this.stateService.href('contacts.contact', { contactId })}>…</a>`;
}