UNPKG

@bespunky/angular-zen

Version:

The Angular tools you always wished were there.

184 lines (174 loc) 7.74 kB
import { inject, InjectionToken } from '@angular/core'; import { Router, provideRouter, provideRoutes } from '@angular/router'; const _RouteComposer_ = Symbol('RouteComposer'); const _NavigatorXToken_ = Symbol('NavigatorXToken'); /** * Injects the auto-generated strongly-typed navigation service for the specified route tree. * * For this to work, the route must be previously generated using {@link routeConfigFor `routeConfigFor`}, and provided into Angular using * either {@link provideRouterX `provideRouterX`} or {@link provideRoutesX `provideRoutesX`}. * * To change the default composed name of a specific auto-generated navigation method, go to the corresponding * route config and add the `friendlyName` propertyName. * * @export * @template Route * @template Entity * @template Root * @template FullPath * @template ComposerName * @param {(Route & WithRouteComposer<Entity, FullPath, ComposerName> & WithNavigationX<Route, Entity, Root>)} route * @return {AutoNavigateRouteMethods<Route, Entity, Root>} */ function useNavigationX(route) { return inject(route[_NavigatorXToken_]); } function touchFirstLetter([firstLetter, ...rest], touch) { return firstLetter ? touch(firstLetter) + rest.join('') : ''; } function firstUpper(value) { return touchFirstLetter(value, first => first.toUpperCase()); } // TODO: Warn if multiple composers with the same name were found function collectRouteComposersByAutoNavigatorName(route) { const composer = route[_RouteComposer_]; const autoNavigatorName = `to${firstUpper(composer.name)}`; const childComposers = collectArrayRouteComposersByAutoNavigatioName(route.children); return new Map([ [autoNavigatorName, composer], ...(childComposers !== null && childComposers !== void 0 ? childComposers : []) ]); } function collectArrayRouteComposersByAutoNavigatioName(routes) { return routes === null || routes === void 0 ? void 0 : routes.map(collectRouteComposersByAutoNavigatorName).reduce((allNestedComposers, childComposers) => new Map([...allNestedComposers, ...childComposers]), new Map()); } function extractArgsFromPath(path) { return path .split('/') .filter(segment => segment.startsWith(':')); } function attemptToProduceAutoNavigationFunctionFor(router, composer) { if (!composer) return undefined; if (composer.hasArgs) { const compose = composer.compose.bind(composer); return (entity) => router.navigateByUrl(compose(entity)); } return () => router.navigateByUrl(composer.compose()); } function createNavigationXFactoryProvider(route) { return { provide: route[_NavigatorXToken_], deps: [Router], useFactory: (router) => { const composers = collectRouteComposersByAutoNavigatorName(route); return new Proxy({}, { get: (_, propertyName) => attemptToProduceAutoNavigationFunctionFor(router, composers.get(propertyName)), has: (_, propertyName) => composers.has(propertyName) }); } }; } function provideNavigatorsFor(...routes) { if (!(routes === null || routes === void 0 ? void 0 : routes.length)) throw `No routes were provided.`; return routes.map(createNavigationXFactoryProvider); } /** * Wraps Angular's `provideRouter` function and adds providers for the navigation-x module. * * @export * @param {NavigationXRoute<any>[]} routes The Angular routes config tree to initialize navigation-x for. * @param {...NoHead<Parameters<typeof provideRouter>>} features Addtional features to pass into Angular's `provideRouter` function. * @return {Provider[]} The providers returned by `provideRouter`, along with other providers needed for navigation-x to work. */ function provideRouterX(routes, ...features) { return [ ...provideRouter(routes, ...features), ...provideNavigatorsFor(...routes) ]; } /** * Wraps Angular's `provideRoutes` function and adds providers for the navigation-x module. * * @export * @param {...NavigationXRoute<any>[]} routes The Angular routes config tree to initialize navigation-x for. * @return {Provider[]} The providers returned by `provideRoutes`, along with other providers needed for navigation-x to work. */ function provideRoutesX(...routes) { return [ ...provideRoutes(routes), ...provideNavigatorsFor(routes) ]; } class RouteComposer { constructor(path, name) { this.path = path; this.name = name; this.args = extractArgsFromPath(path); this.hasArgs = this.args.length > 0; this.compose = (this.hasArgs ? this.routePathWithArgs.bind(this) : this.routePath.bind(this)); } routePathWithArgs(entity) { return this.args.reduce((route, arg) => { const argName = arg.substring(1); const value = entity[argName]; const formattedValue = value instanceof Date ? value.toUTCString() : String(value); return route.replace(arg, formattedValue); }, this.path); } routePath() { return this.path; } } const autoNavigatorNameSeparator = ''; function generateRouteComposerName(path) { return path .replace(/\/:/g, '/') .split('/') .map(firstUpper) .join(autoNavigatorNameSeparator); } /** * Creates a strongly-typed configurator for Angular routes. * * Call this with an entity, then generate a strongly typed Angular route config tree using one of the * configurator's functions. * * Store the generated route somewhere and use `provideRouterX()` or `provideRoutesX()` to tell Angular about it. * * @export * @template Entity The entity (or data structure) route arguments should match with. * @return {RouteConfigurator<Entity>} An object which allows generating strongly typed routes. */ function routeConfigFor() { function combinePath(root, segment) { return (segment ? `${root}/${segment}` : root); } function prefixedRouteCore(route, root) { var _a, _b; const path = combinePath(root, route.path); const composerName = ((_a = route.friendlyName) !== null && _a !== void 0 ? _a : generateRouteComposerName(path)); const composer = new RouteComposer(path, composerName); const children = (_b = route.children) === null || _b === void 0 ? void 0 : _b.map(child => prefixedRoute(child, path)); return Object.assign(Object.assign({}, route), { children, [_RouteComposer_]: composer }); } function prefixedRoute(route, root) { return Object.assign(Object.assign({}, prefixedRouteCore(route, root)), { [_NavigatorXToken_]: new InjectionToken(`_ROUTER_X_NAVIGATION__${route.path}`) }); } function route(route) { // When the `root` arg was optional with a default value of `''`, TS appended `${string}` (e.g. `${string}/theaters/:theaterId`) // to the route template for some reason, which breaks the arg extraction later on. // Passing `''` manually however, leaves the template as is should be (e.g. `/theaters/:theaterId`) and allows the string template to be interpreted well. // `route()` and `prefixedRoute()` were seperated to allow the developer to call `route()` without providing `''`. // The `prefixedRoute()` is exposed to the world as it might be usefull in the future (e.g. lazy loaded routes? maybe...?) return prefixedRoute(route, ''); } return { route, prefixedRoute }; } /** * Generated bundle index. Do not edit. */ export { autoNavigatorNameSeparator, provideRouterX, provideRoutesX, routeConfigFor, useNavigationX }; //# sourceMappingURL=bespunky-angular-zen-router-x-navigation.mjs.map