VirtualRoute
Class for creating routes with custom activation logic. Useful for implementing:
- Modal windows routing
- Feature toggles
- Conditional UI states
- Non-URL-based routing scenarios
Unlike Route, VirtualRoute does not parse URL path declarations. Open state is controlled by your handlers and checkOpened.
Constructor
createVirtualRoute(config?: VirtualRouteConfiguration<TParams>)
new VirtualRoute(config?: VirtualRouteConfiguration<TParams>)Simple example:
const profileDialogRoute = createVirtualRoute<{ userId: string }>();
await profileDialogRoute.open({ userId: '42' });
profileDialogRoute.isOpened; // true
profileDialogRoute.params; // { userId: '42' }
await profileDialogRoute.close();
profileDialogRoute.isOpened; // false
profileDialogRoute.params; // nullCustom open close state handling:
const ageModalRoute = createVirtualRoute<{ age: number }>({
initialParams: { age: 0 },
checkOpened: (route) => route.query.data.showAgeModal === 'true',
open: (params, route) =>
route.query.update({
...params,
showAgeModal: 'true',
}),
close: (route) =>
route.query.update({
showAgeModal: undefined,
}),
});Properties and methods
isOpened computed
Main flag for UI: true means route is currently open.
const dialogRoute = createVirtualRoute({
checkOpened: (route) => route.query.data.modal === 'profile',
});
dialogRoute.isOpened; // use this in components to show/hide dialogisOpening computed
true while async opening logic is still running.
const slowRoute = createVirtualRoute({
open: async () => {
await new Promise((r) => setTimeout(r, 400));
},
});
slowRoute.open();
slowRoute.isOpening; // true for a short timeisClosing computed
true while closing is in progress.
const route = createVirtualRoute({
close: () => {
localStorage.setItem('lastModal', 'closed');
},
});
route.close();
route.isClosing; // true while close is being processedparams observable
Stores current route params; after successful close becomes null.
const panelRoute = createVirtualRoute<{ tab: 'info' | 'settings' }>();
await panelRoute.open({ tab: 'settings' });
panelRoute.params?.tab; // 'settings'
await panelRoute.close();
panelRoute.params; // nullquery
Access to query params object used by this route.
const filtersRoute = createVirtualRoute({
checkOpened: (route) => route.query.data.filters === '1',
});
filtersRoute.query.update({ filters: '1' });open(params?, extra?) action
Opens the route manually and optionally updates query.
const profileRoute = createVirtualRoute<{ userId: string }>();
await profileRoute.open(
{ userId: '42' },
{ query: { modal: 'profile' }, replace: true },
);close() action
Closes the route manually.
const helpRoute = createVirtualRoute({
checkOpened: (route) => route.query.data.modal === 'help',
});
await helpRoute.close();setOpenChecker() action
Changes open condition at runtime.
const route = createVirtualRoute();
route.setOpenChecker((r) => r.query.data.modal === 'settings');isOuterOpened observable.ref
Raw result of current checkOpened. Usually needed only for debugging.
const route = createVirtualRoute({
checkOpened: (r) => r.query.data.modal === 'x',
});
console.log(route.isOuterOpened);Configuration
Interface: VirtualRouteConfiguration<TParams>.
abortSignal
Abort signal for route lifecycle cleanup.
const controller = new AbortController();
const route = createVirtualRoute({
abortSignal: controller.signal,
});
controller.abort();queryParams
Custom query params implementation for this route.
const route = createVirtualRoute({
queryParams: myCustomQueryParams,
});initialParams
Initial params value or factory (route) => params.
const route = createVirtualRoute<{ page: number }>({
initialParams: { page: 1 },
});checkOpened
Function (route) => boolean that defines external open state.
const route = createVirtualRoute({
checkOpened: (r) => r.query.data.modal === 'auth',
});getAutoOpenParams
Called when checkOpened turns true and route auto-opens.
Example:
const modalRoute = createVirtualRoute<{ id: string | null }>({
checkOpened: (route) => Boolean(route.query.data.modalId),
getAutoOpenParams: (route) => ({
params: {
id: String(route.query.data.modalId ?? ''),
},
extra: {
query: { modal: 'true' },
replace: true,
},
}),
});beforeOpen
Hook (params, route) => void | boolean | Promise<void | boolean>. Return false to reject opening.
Example:
const authModalRoute = createVirtualRoute<{ redirectTo?: string }>({
checkOpened: (route) => route.query.data.modal === 'auth',
beforeOpen: (params) => {
if (!auth.canOpenModal) {
return false;
}
if (params?.redirectTo && !params.redirectTo.startsWith('/')) {
return false;
}
},
});open
Custom open handler (params, route) => void | boolean | Promise<void | boolean>. Return false to reject opening.
const route = createVirtualRoute<{ id: string }>({
open: (params, r) => {
r.query.update({ modalId: params?.id });
},
});afterOpen
Hook called after successful opening.
const route = createVirtualRoute({
afterOpen: () => {
analytics.track('modal_opened');
},
});beforeClose
Hook () => void | boolean | Promise<void | boolean>. Return false to reject closing.
Example:
const editorModalRoute = createVirtualRoute({
checkOpened: (route) => route.query.data.modal === 'editor',
beforeClose: () => {
if (hasUnsavedChanges) {
return false;
}
},
});close
Custom close handler (route) => void | boolean. Return false to reject closing.
const route = createVirtualRoute({
close: (r) => {
r.query.update({ modal: undefined });
},
});afterClose
Configuration field exists in API, but current implementation does not call it.
const route = createVirtualRoute({
afterClose: () => {
// currently not called by implementation
},
});meta
Arbitrary metadata in configuration object (not exposed as a VirtualRoute instance field).
Example:
const route = createVirtualRoute({
meta: {
memes: true,
},
});