Achieving nested type parameter usage in TypeScript without the need to declare it repeatedly

Have you ever wondered if it's possible to achieve something similar to this in TypeScript?

If not, is there a documented reason explaining why the compiler struggles with inferring nested type parameters?

Let me provide an illustrative example of what I'm aiming for:

interface TestFilter {
    name?: string;
}

interface FilterComponent<F> {
    setFilter(filter: F)
}

class TestFilterComponent implements FilterComponent<TestFilter> {
    private filter: TestFilter;

    setFilter(filter: TestFilter) {
        this.filter = filter;
    }
}

// Is there a way to utilize F without establishing it as a distinct type parameter?
// For instance: class FilterWrapperComponent<FC extends FilterComponent<F>>
abstract class FilterWrapperComponent<F, FC extends FilterComponent<F>> {
    private sidebarFilter: FC;
    private modalFilter: FC;

    public passFilter(filter: F) {
        this.sidebarFilter.setFilter(filter);
        this.modalFilter.setFilter(filter);
    }
}

// All I want is to directly specify "extends FilterWrapperComponent<TestFilterComponent>"
// and have TestFilter automatically assigned to F
class TestFilterWrapperComponent extends FilterWrapperComponent<TestFilter, TestFilterComponent> {

}

You can also experiment with this code snippet on the TypeScript playground.

Answer №1

Utilizing the infer statement opens up possibilities to deduce types nested within conditional types, as demonstrated in this code snippet.

This code introduces the UnwrapFilter function, which extracts the generic argument from a specified type (FilterComponent).

type UnwrapFilter<T extends FilterComponent<any>> = T extends FilterComponent<infer TC> ? TC : never;

abstract class FilterWrapperComponent2<FC extends FilterComponent<any>> {
    constructor(
      private sidebarFilter: FC,
      private modalFilter: FC,
    ) {}

    public passFilter(filter: UnwrapFilter<FC>) {
        this.sidebarFilter.setFilter(filter);
        this.modalFilter.setFilter(filter);
    }
}

Exploring a more general version of the UnwrapFilter function not tied to a specific FilterComponent type proved challenging due to the nature of the infer statement.

interface OneParamGeneric<T> {}
type UnwrapOneParam<T extends OneParamGeneric<{}>> = T extends OneParamGeneric<infer TypeArg> ? TypeArg : never;

type T1 = UnwrapOneParam<OneParamGeneric<TestFilter>>;
type T2 = UnwrapOneParam<FilterComponent<TestFilter>>;


In some cases, using a type query - also known as an Indexed Access Types - can provide an alternative approach.

The PublicFilterComponent interface showcases accessing the activeFilter field using square brackets as shown here:

interface PublicFilterComponent<F> {
    public readonly activeFilter: F;
    setFilter(filter: F): void;
}

abstract class FilterWrapperComponent3<FC extends PublicFilterComponent<any>> {
    constructor(
      private sidebarFilter: FC,
      private modalFilter: FC,
    ) {}

    public passFilter(filter: FC['activeFilter']) {
        this.sidebarFilter.setFilter(filter);
        this.modalFilter.setFilter(filter);
    }
}

Answer №2

When a parameter is left out, its default value is set to `{}`. However, this default value can be altered using generic parameter defaults.

Simply stating "extends FilterWrapperComponent" should automatically assign TestFilter as F

In a reverse scenario, both

FilterComponent<TestFilter>
and TestFilter can be obtained by specifying only TestFilter:

abstract class FilterWrapperComponent<F, FC = FilterComponent<F>> {}

class TestFilterWrapperComponent extends FilterWrapperComponent<TestFilter> {}

Answer №3

For those using TypeScript 2.8, which is currently in the release candidate phase and can be accessed by installing typescript@rc, there are exciting new features to explore.

One of the key additions in TypeScript 2.8 is support for conditional types and type inference related to conditional types. While direct type inference in generic constraints isn't possible, a clever workaround involves utilizing conditional types that result in never in certain scenarios.

interface FilterComponent<F = any> {
    setFilter(filter: F)
}

type ExtractFilter<C extends FilterComponent> = C['setFilter'] extends (filter: infer F) => void ? F : never;

This allows for more flexibility when defining classes:

abstract class FilterWrapperComponent<FC extends FilterComponent> {
    private sidebarFilter: FC;
    private modalFilter: FC;

    public passFilter(filter: ExtractFilter<FC>) {
        this.sidebarFilter.setFilter(filter);
        this.modalFilter.setFilter(filter);
    }
}

class TestFilterWrapperComponent extends FilterWrapperComponent<TestFilterComponent> {
}

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

Leveraging AngularJS services within an Angular service

I am currently in the process of transitioning my AngularJS application to Angular. To facilitate this transition, I plan to create a hybrid application that combines both frameworks until the conversion is complete. However, I have encountered an issue wi ...

What could be the reason for the 'tsc' command not functioning in the Git Bash terminal but working perfectly in the command prompt?

I recently installed TypeScript globally on my machine, but I am facing an issue while trying to use it in the git bash terminal. Whenever I run tsc -v, I encounter the following error: C:\Users\itupe\AppData\Roaming\npm/node: l ...

Utilizing Angular 2 or TypeScript to Retrieve Visitor's Location

I initially began using ServerVariables["HTTP_CF_IPCOUNTRY"] on the backend server, but it's proving to be too slow. I'm looking for an Angular or TypeScript alternative to speed things up. ...

Object.assign versus the assignment operator (i.e. =) when working with React components

Just a quick question: I've come across some answers like this one discussing the variances between Object.assign and the assignment operator (i.e. =) and grasp all the points made such as object copying versus address assignment. I'm trying to ...

Tips for receiving string body parameters from Express routes in TypeScript instead of using the 'any' type?

I have a situation where I am passing a unique identifier called productId as a hidden input within a form: <form action="/cart" method="POST"> <button class="btn" type="submit">Add to Cart</button ...

What is the process for transforming a string literal type into the keys of a different type?

Imagine having a string literal type like this: type Letters = "a" | "b" | "c" | "d" | "e"; Is there a way to create the following type based on Letters? type LetterFlags = {a: boolean, b: boolean, c: bool ...

The page you are trying to access does not support the POST method. It seems that there are no actions available

In the +page.svelte file located at route/manager, I have a form setup as follows: <form id="file_selection" action="?/manageFile" method="post" use:enhance={() => { waiting = true; return async ({update, res ...

How can I choose one record based on another record in a database?

I have a unique structure where consts and record types are combined: const PageType = [ 'type1', 'type2', 'type3' ] as const; export type PageType = typeof PageType[number]; interface PageInfo { title: string } int ...

The essential guide to creating a top-notch design system with Material UI

Our company is currently focusing on developing our design system as a package that can be easily installed in multiple projects. While the process of building the package is successful, we are facing an issue once it is installed and something is imported ...

Transforming a base64 string into a uint8Array or Blob within Typescript/Angular8

I'm new to handling Base64 encoded strings, uint8Array, and Blobs. I've integrated a pdf viewer library from this repository https://github.com/intbot/ng2-pdfjs-viewer into our Angular 8 web application. I'm facing an issue where I am sendin ...

Retrieve the array from the response instead of the object

I need to retrieve specific items from my database and then display them in a table. Below is the SQL query I am using: public async getAliasesListByDomain(req: Request, res: Response): Promise<void> { const { domain } = req.params; const a ...

Want to enhance user experience? Simply click on the chart in MUI X charts BarChart to retrieve data effortlessly!

I'm working with a data graph and looking for a way to retrieve the value of a specific column whenever I click on it, and then display that value on the console screen. Check out my Data Graph here I am using MUI X charts BarChart for this project. ...

What sets apart a JSON Key that is enclosed in double quotes "" from one that has no quotes?

Below is my TypeScript object: { first_name:"test", last_name: "test", birthdate:"2018-01-08T16:00:00.000Z", contactNumber: "12312312312", email:"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e19 ...

I configured metro.config.js to utilize NativeWind for styling and SVG transformer for optimizing

I've been spending countless hours trying to figure out how to combine NativeWind with an SVG reader in a React Native app, and finally, I've found the solution. ...

What could be causing the undefined properties of my input variables in Angular?

Currently, I am fetching data from a service within the app component and passing it down to a child component using @Input. Oddly enough, when I log the data in ngOnInit, it appears correctly in the child component. However, when I try to assign it to a v ...

Unable to access variables of nested components in Angular 2 templates

Recently delving into Angular2, I've encountered a simple issue. My aim is to create a basic parent component that acts as a container for dynamic boxes, each with its own properties and data. Here's what I've accomplished so far: The con ...

Angular 11 throws an error stating that the argument of type 'null' cannot be assigned to a parameter of type 'HttpClient'

As I embark on my journey to becoming a developer, I encounter a problem when passing a null argument as shown below: todos.component.spec.ts import { TodosComponent } from './todos.component'; import { TodoService } from './todo.servic ...

Tips for transferring data to an entry component in Angular 2 using ng-bootstrap modal

I've been grappling with sending data to a custom modal content component in Angular 2. My objective is to have the flexibility of calling this modal from any other component without duplicating code. Despite my efforts, including referencing the "Com ...

Encountering a problem with updating values in local storage using ReactJS

My goal is to store values in local storage, but I am facing an issue where it saves an empty array in local storage the first time I click on Set Item. After the initial setup, the code works as expected. I am relatively new to React and TypeScript. Below ...

The integration of reduce and scan with a specific focus

RxJS version: 5.5.2 Working with an array const v = [1, 2, 3]; The goal is to convert this array into a Subject initially acting like an Observable until all values (1, 2, 3) are consumed. After that, it should behave like a Subject. The challenge lies ...