Tips for incorporating conditional types into function parameters based on existing input

The title might be confusing, so allow me to clarify.

My Objective
I am referring to the code snippet below. I aim to specify the route property based on the types property as either a string or a function that returns a string.

The Code
Let's begin with a functional example.

// Utilizing TypeScript to define types without actual values provided
const defineArgTypes = <
  T extends {
    args?: ArgumentsBase | null;
  }
>() => null as any as T;

interface ArgumentsBase {
  queryParams?: Record<string | number | symbol, any>;
}

type BaseActionMap = Record<
  string,
  {
    types?: { args?: ArgumentsBase };
  }
>;

type ActionMapItem<Item extends BaseActionMap[string]> = {
  types?: Item['types'];
};

type ActionMap<BaseConfig extends BaseActionMap> = {
  [Property in keyof BaseConfig]: ActionMapItem<BaseConfig[Property]>;
};

type ConfigMapItem<Item extends BaseActionMap> = {
  route: Item['types'] extends { args: ArgumentsBase }
    ? (args: Item['types']['args']['queryParams']) => string
    : string;
};

type ConfigMap<AMap extends ActionMap<any>> = {
  [Property in keyof AMap]: ConfigMapItem<AMap[Property]>;
};

const defineActions = <Data extends BaseActionMap>(
  actions: Data,
  config: ConfigMap<Data>
) => {
  // irrelevant code nested here
};

const config = defineActions(
  {
    getTodos: {},
    getTodo: {
      types: defineArgTypes<{ args: { queryParams: { id: string } } }>(),
    },
  },
  {
    getTodo: {
      route: (d) => `todos/${d.id}`,
    },
    getTodos: {
      route: 'todos',
    },
  }
);

In the above code, it is necessary to define "actions (getTodos, getTodo)" twice.

Is there a way to streamline this to the following while maintaining conditional types for the route properties?

const config = defineActions(
  {
    getTodos: {
      route: 'todos',
    },
    getTodo: {
      types: defineArgTypes<{ args: { queryParams: { id: string } } }>(),
      route: (d) => `todos/${d.id}`,
    },
  }
);

Answer №1

If you're searching for a solution, consider implementing a discriminated union in Typescript. This type is essentially a combination of two types that share a common property known as the "discriminant." This allows Typescript to narrow down the union based on this property. For instance, here's a simplified example of how you can define a discriminated union:

type Config = {
  types?: undefined
  route: string
} | 
{
  types: {id:string}
  route: (args:{id:string})=>string
}

In this case, the types property acts as the discriminant. If it's undefined, Typescript will infer the first member of the union. If it's {id:string}, Typescript will narrow it down to the second member. You can extend this concept to include more options as needed.

To leverage the typing from types in the route function, you can use generics like so:

type Config<T extends {id: string}> = {
  types?: undefined
  route: string
} | 
{
  types: T
  route: (args:T)=>string
}

After defining your discriminated union, you can use it within your defineActions function by specifying appropriate type parameters. Here's an example:

function defineActions<T extends {id:string}, U extends Record<string,Config<T>>>(configs:U){}

const config = defineActions(
  {
    getTodos: {
      route: 'todos',
    },
    getTodo: {
      types: { id: "myId" },
      route: (d) => `todos/${d.id}`,
    },
  }
);

You can also test this implementation in a playground.

One thing to note: The way TS determines the discriminant may not always be explicit. To ensure proper discrimination based on your intended property (in this case, types), you may need to differentiate other parts of the type definition as well. However, it might not be necessary depending on your specific requirements.

I've provided a simplified explanation tailored to your query. Feel free to ask if you need further clarification or adjustments.

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

Stop WebStorm from automatically importing code from a different Angular project within the same workspace

I currently have an angular repository that consists of two projects: a library and an Angular application. To link the library to my project, I utilized the npm link command. Within the package.json file, I specified the entry as follows: ... "my-lib ...

"TypeScript Static Classes: A Powerful Tool for Struct

In my TypeScript code, there is a static class with an async build method as shown below: export default class DbServiceInit { public static myProperty: string; public static build = async(): Promise<void> => { try { ...

Creating a Typescript interface for a anonymous function being passed into a React component

I have been exploring the use of Typescript in conjunction with React functional components, particularly when utilizing a Bootstrap modal component. I encountered some confusion regarding how to properly define the Typescript interface for the component w ...

Turn off the button and add a CSS class to it when sending a message

I have an Angular 7 component with a form that includes the following TypeScript code: export class MessageComponent implements OnInit { message: FormGroup; constructor(private formBuilder: FormBuilder, private messageService: MessageService) { } ...

Having trouble receiving a blob response using HttpClient POST request in Angular 9?

I'm encountering an issue while attempting to receive a zip file as a blob from an HTTP POST request. However, the resolved post method overload is not what I expected. const options = { responseType: 'blob' as const }; Observable<Blob ...

Is there a more efficient method to tally specific elements in a sparse array?

Review the TypeScript code snippet below: const myArray: Array<string> = new Array(); myArray[5] = 'hello'; myArray[7] = 'world'; const len = myArray.length; let totalLen = 0; myArray.forEach( arr => totalLen++); console.log(& ...

Create and export a global function in your webpack configuration file (webpack.config.js) that can be accessed and utilized

Looking to dive into webpack for the first time. I am interested in exporting a global function, akin to how variables are exported using webpack.EnvironmentPlugin, in order to utilize it in typescript. Experimented with the code snippet below just to und ...

The search for 'Renderer2' in '@angular/core' did not yield any results

After successfully installing Angular Material in my Angular Project by following the instructions provided in the Material documentation, I encountered some issues. Specifically, when attempting to launch the application with 'npm start', I star ...

Error message: Property is not found in the $rootScope object in AngularJS

Encountering an issue while attempting to assign a value to the rootscope in Typescript. class TestClass{ this.rootScope: ng.IRootScopeService; constructor($rootScope){ this.rootScope = $rootScope; } addValueToRoot=()=>{ ...

I am unable to locate the type definition file for 'core-js' at the moment

Currently, I am in the process of developing an application using angular2 along with angular-cli. Surprisingly, angular-in-memory-web-api was not included by default. In order to rectify this, I took the initiative to search for it and manually added the ...

What steps can I take to guarantee that the observer receives the latest value immediately upon subscribing?

In my Angular 2 and Typescript project, I am utilizing rxjs. The goal is to share a common web-resource (referred to as a "project" in the app) among multiple components. To achieve this, I implemented a service that provides an observable to be shared by ...

Rendering Information in Angular 4 Through Rest API

Encountering issues displaying data from my local express.js REST API, organized as follows: people: [{ surname: 'testsurname', name: 'testname', email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemai ...

Encountering a "subscribe is not a function" error while attempting to utilize a JSON file in Angular 2

My attempt to import a JSON file into my service is resulting in the following error: Error: Uncaught (in promise): TypeError: this._qs.getBasicInfo(...).subscribe is not a function(…) The current state of my service file is as follows @Injectable() ...

TypeORM find query is returning a data type that does not match the defined entity type

In my infrastructure module, I am using the code snippet below: import { Student } from "core" import { Repository } from "./Repository" import { Database } from "../../db" export class UserRepository<Student> extends Re ...

add the string to the chat messages array in the observable

Currently, I am in the process of developing a chat application and my goal is to showcase the user's messages in the chatroom, referred to as the feed in this project. I have already implemented a function called getMessages() that displays all exist ...

Guide on displaying the length of an observable array in an Angular 2 template

I am working with an observable of type 'ICase' which retrieves data from a JSON file through a method in the service file. The template-service.ts file contains the following code: private _caseUrl = 'api/cases.json'; getCases(): Obs ...

Ways to ensure that when changes occur in one component, another component is also updated

How can I update the cart badge value in the navbar component every time a user clicks the "Add to Cart" button in the product-list component? The navbar component contains a cart button with a badge displaying the number of products added to the cart. n ...

Incorporate SVG files into a TypeScript React application using Webpack

I am trying to incorporate an SVG image into a small React application built with TypeScript and bundled using Webpack. However, I am encountering an issue where the image is not displaying properly (only showing the browser's default image for when n ...

Alert me in TypeScript whenever a method reference is detected

When passing a function reference as a parameter to another function and then calling it elsewhere, the context of "this" gets lost. To avoid this issue, I have to convert the method into an arrow function. Here's an example to illustrate: class Mees ...

Cannot compile Angular 4 Material table: Encountering unexpected closing tag

Currently, I am working on an Angular 4 Project that involves using Material. The main task at hand is to implement a table from Angular Material. However, the issue I am facing is that the table does not compile as expected. Here's the HTML code sni ...