Create mapped types based on a constant object

I am working on defining Angular routing and pages using an object. Some of the paths in the object have parameters that can be replaced:

const ROUTES = {
  PAGE_NO_PARAMS: '/hello/page/two',
  PAGE_R: '/about/:id',
  PAGE_Z: '/page/page/:param/:id',
  PAGE_N: '/who/:x/:y/:z/page',
} as const

My goal is to create a type that represents valid page types from this object while ignoring the params (:xxx) in the path with some sort of wildcard. Currently, my type check does not match paths with resolved params like /about/xxxxx:

type ValidRoute = '/hello/page/two' | '/about/${string}' | '/page/page/${string}/${string}'| '/who/${string}/${string}/${string}/page'

To address this issue, I want to iterate through each property in ROUTES and create a type that replaces /:any string/ with /${string}.

This approach will help me catch build errors instead of runtime errors.

Answer №1

When we come across :name in the path, we can utilize recursive conditional types to replace it with $string}

type MakeValidRoute<T extends string, R extends string = ''> =
  T extends `${infer Head}/:${infer Name}/${infer Tail}`?
    MakeValidRoute<<`/${Tail}`, `${R}${Head}/${string}`>:
  T extends `${infer Head}/:${infer Name}`?
    `${R}${Head}/${string}`:
    `${R}${T}`

Check Playground Link

We have implemented tail recursive conditional types to enhance the performance of these types within the compiler

Answer №2

Another response mentioned earlier is indeed accurate. Allow me to share my approach:

type SubstituteParamNames<T extends string, S extends string = string, A extends string = ""> =
  T extends `${infer F}:${infer _}/${infer R}` ? SubstituteParamNames<R, S, `${A}${F}${S}/`>
  : T extends `${infer F}:${infer _}` ? `${A}${F}${S}` : `${A}${T}`

The SubstituteParamNames<T, S> type is an example of a recursive conditional type with tail recursion elimination, which replaces all path fragments separated by colons (beginning with a ":" and ending with a "/" or the end of the string) in T with the value in S, defaulting to just string.

This leads to the following result:

const ROUTES = {
  PAGE_NO_PARAMS: '/hello/page/two',
  PAGE_R: '/about/:id',
  PAGE_Z: '/page/page/:param/:id',
  PAGE_N: '/who/:x/:y/:z/page',
} as const

type Routes = SubstituteParamNames<typeof ROUTES[keyof typeof ROUTES]>
/* type RP = type Routes = "/hello/page/two" | `/about/${string}` | 
 `/page/page/${string}/${string}` | `/who/${string}/${string}/${string}/page`
*/

If required, you can swap out string for something else:

type IfImportant = SubstituteParamNames<"/ay/:bee/cee/:dee/eee", "XXX">
// type IfImportant = "/ay/XXX/cee/XXX/eee"

Access the code on Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

404 Error: Unable to Locate Socket Io

I'm currently working on implementing a chat feature in Angular 2 using Socket IO, following this tutorial. However, I encountered an error message during a test on the server: GET http://localhost:3000/socket.io/?EIO=3&transport=polling& ...

What is the process for utilizing Angular's emulated encapsulation attributes on HTML elements that are dynamically added to an Angular component?

Within my custom Angular component, a third-party library is dynamically adding an HTML element. I am seeking a solution that can apply Angular's encapsulation attributes to these elements regardless of the specific third-party library being used. If ...

Having trouble deploying Firebase Cloud function following the migration to Typescript

After following the steps outlined in the firebase documentation to convert my cloud functions project to TypeScript (see https://firebase.google.com/docs/functions/typescript), I encountered an error when attempting to deploy using 'firebase deploy - ...

Guide to setting up Bootstrap in your Angular application

Currently attempting to incorporate bootstrap into my angular project. Here is the command I am inputting: npm install bootstrap jquery --save The response I receive is as follows: up to date, audited 1013 packages in 3s 92 packages are seeking fundin ...

The conflict between Apple App Site Association and an angular route is causing issues

Can someone provide guidance on setting up an iOS app link to work with Angular? My goal is to include a link in emails sent to users that will open the app if it's installed. I've placed a file named 'apple-app-site-association' witho ...

Just started a fresh Angular project, but the default page served by the 'ng serve' command isn't quite what

Last week, I delved into the world of Angular and kicked off a new project with ng new. After firing up the project with ng serve, I was greeted by a page that looked like this: ng serve default project page The app still functions properly, but the layou ...

Trouble encountered with the implementation of setValue on placeholder

When I send the value for age, it is being treated as a date in the API that was built that way. However, when I use setValue to set the form value and submit the form, it also changes the placeholder text, which is not what I want. I would like the placeh ...

Oops! It appears that there is an issue with the 'value' property in Vue3

I encountered an issue while trying to reference a state variable through the store mechanism import { AppState } from '@/types' import { localStorage } from '@/utils/storage'; import { defineStore } from 'pinia'; import { get ...

Unfortunately, I am unable to utilize my Async Validator as it is wrapped within the "__zone_symbol" object

I have created an asynchronous validator for passwords. export class PasswordsValidators{ static oldPasswordMatch(control: AbstractControl) : Promise<ValidationErrors> | null { return new Promise((resolve) => { if(control. ...

Leverage pipes within Angular 2 rc.6 components

Before the rc.5, I used to initialize my custom pipe in the component like this: private myCustomPipe = new CustomPipe() this.myCustomPipe.transform(...) However, after migrating the application, I encountered an error with the new CustomPipe() instantia ...

Guide to retrieving the file path of the current module within a Node.js package?

I need to ensure that the correct filepath is printed, even if the function is imported in another module, to handle errors properly. How can I achieve this while working with a serverless stack? Here is an example code snippet: class Logger { filePa ...

"The response from an http.post request in Angular 2 is displaying

I'm struggling with a problem that seems impossible to solve. I understand Make no assumptions about the server API. Not all servers return an object with a data property. but I have no clue on how to handle it. How can I send data to: dat ...

Webpack: The command 'webpack' does not exist as a recognized cmdlet, function, script file, or executable program

Attempting to set up a new project using webpack and typescript, I have created the project along with the webpack file. Following the instructions on the webpack website, I successfully installed webpack using npm install webpack webpack-cli --save-dev ...

Potential issue with Lodash's _.once function: the possibility of a race condition

Here's an example of code that demonstrates a scenario: const fetch = _.once(myRealFetch) const queue = new PQueue({concurrency: 1000}); queue.add(function() { const result = fetch() // Rest of the code ... }) queue.add(function() { const resul ...

Calculating the total of all values in a table

For my ngFor loop, the invoice total is calculated based on price and hours, but I also want to calculate the totals of all invoices in the end. <tr *ngFor="let invoice of invoiceItem.rows"> <td>{{ invoice.rowName }}</td> <td& ...

What is the process for manually running a parent route resolver in Angular 2?

I am working with routes, specifically one parent route and several children. The parent router includes a Resolve function. {path: ':orderId', component: OrderComponent, resolve: {order: OrderResolver}, children: [...]} When all routes ...

Validating dynamic forms with multiple rows in Angular 9

I am looking for a way to consolidate validation errors in one place for multiple rows created dynamically based on user input. Instead of displaying the error message next to each field, I want all the validation errors for each row to be displayed collec ...

Encountering challenges while integrating Angular with a Laravel forum creation project

Currently, I am working on building a forum application that involves users, posts, and comments using Laravel. However, the next step in my project requires integrating Angular, which is new territory for me and I'm not sure where to start. I have a ...

Unlocking the secrets of retrieving the URL query string in Angular2

I'm struggling to extract the URL query string returned by the security API, resulting in a URL format like this: www.mysite.com/profile#code=xxxx&id_token=xxx. My goal is to retrieve the values of code and id_token. In my ngOnInit() function, I ...

Can Prisma query by Uuid?

I am working on synchronizing a postgresql database with data from a remote API at regular intervals. The script will make API calls at scheduled times, process the responses, and update the database with the relevant information for each record. Below i ...