Using Typescript for the factory design pattern

My goal is to develop a factory for generating instances of MainType. To achieve this, I want to reuse existing types (specifically the same instance) which are stored in the ItemFactory.

class BaseType {

}

class MainType extends BaseType {

}

class ItemFactory {
    items: { [type: string]: BaseType } = {};

    get<T extends BaseType>(type: string): T | null {
        let item = this.items[type];

        if (!item) {
            switch (type) {
                case "main-type":
                    item = new MainType();
                    break;
                default:
                    return null;
            }

            this.items[type] = item;
        }

        return item as T;
    }
}

Is there a way to simplify the calling process?

itemFactory.get<MainType>("main-type"); // current call

// option 1
const resolvedType = itemFactory.get<MainType>();

// option 2
const resolvedType = itemFactory.get("main-type");

I prefer either option 1 or 2 (not both), so that I don't have to provide both an identifier and a type each time I want the resulting type to be correctly resolved.

Answer №1

To ensure the compiler understands the relationship between names passed to itemFactory.get() and the expected output type, you must provide a mapping. Utilizing interfaces for name-type mappings is an effective strategy:

interface NameMap {
  "main-type": MainType;
  // add other name-type mappings here
}

Modify your get() method as follows:

  get<K extends keyof NameMap>(type: K): NameMap[K] | null {
    let item = this.items[type];

    if (!item) {
      switch (type) {
        case "main-type":
          item = new MainType();
          break;
        default:
          return null;
      }

      this.items[type] = item;
    }

    return item as NameMap[K];
  }

Replace occurrences of T extends BaseType with NameMap[K] where K extends keyof NameMap. Following these changes, executing "option 2" will yield the desired result:

const resolvedType = itemFactory.get("main-type"); // MainType | null

Please note that achieving "option 1" is not viable. TypeScript's type system undergoes erasure when translating to JS, thus jeopardizing functionality such as invoking:

itemFactory.get<MainType>();

At runtime, this call simplifies to:

itemFactory.get();

This lack of information prevents the correct return value determination, consistent with TypeScript's design philosophy outlined in the links provided. The recommended approach involves leveraging runtime values like "main-type" instead of compile-time types like MainType when defining the behavior of get().


I hope this explanation clarifies the concept for you. Best of luck!

Link to code

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

I am looking to transfer information from Angular 4 to Java servlet (cross-domain)

Having trouble sending data from Angular 4 to a Java servlet due to access control restrictions. Need to figure out how to properly insert data into the database using the Java servlet. Here is my code snippet: import { Injectable } from '@angular/ ...

Styling in Svelte/TS does not change when applied through a foreach loop

I've been experimenting with creating a unique "bubble" effect on one of my websites, but I'm facing difficulty changing the styling in a foreach loop. Despite no errors showing up in the console, I'm at a loss as to how to effectively debu ...

Issue with Typescript express application utilizing express-openid-connect wherein cookies are not being retained, resulting in an infinite loop of redirects

For a while now, I've been facing a block with no resolution in sight for this particular issue. Hopefully, someone out there can lend a hand. Background I have a TS express application running on Firebase functions. Additionally, I utilize a custom ...

Tips for obtaining the accurate HTML code format using Angular 2's input feature:

I am looking to retrieve all the code with an input as [input] and a tag as #tag. When attempting to obtain HTML code with jQuery using console.log($("#content")[0].outerHTML);, this is an example of how the code looks: <div dnd-droppable [dropZones]= ...

Unexpected behavior in Typescript validation of function return object type

After some investigation, I came to the realization that TypeScript does not validate my return types as anticipated when using the const myFn: () => MyObjType syntax. I ran some tests on the TypeScript playground and examined the code: type MyObj = { ...

What is the process for initiating a local Lambda edge viewer request?

Is there a way to run aws cloudfront lambda edge functions locally and simulate the event in order to observe the response from one of the four functions? I made modifications to the viewerRequest function of lambdaEdge, but I'm wondering if there is ...

Obtaining attributes of a class from an object passed into the constructor

Consider the following code snippet: interface MyInterface { readonly valA: number; readonly valB: number; } class MyClass { readonly valA: number; readonly valB: number; constructor(props: MyInterface) { this.valA = props.val ...

Tips for managing the error message "The key 'myOptionalKey' is optional in the 'myObject' type but necessary in the '{...}' type"

Issue I'm currently working on making a sortable table using a sample table component from Material-UI. I encountered an error when I included an optional key in the Data object. It seems that the type definition in the getComparator function does no ...

What is the process of invoking a service from a controller?

In my MovieSearchCtrl controller class, I have a method called returnMovies(query) that looks like this: returnMovies(query): any { return MovieSeat.MovieSearchService.getMovies(query); } Within the MovieSearchService service class, there is a functi ...

The module './product' could not be located, resulting in error TS2307

app/product-detail.component.ts(2,22): error TS2307: Cannot find module './product'. I have tried several solutions but none of them seem to work for me. I am working on a demo app in Angular 2 and encountering this specific error. Any guidance ...

The Angular router seems to be refusing to show my component

My Angular 2 App includes a Module called InformationPagesModule that contains two lazy load components (Info1 Component and Info2 Component). I would like these components to load when accessing the following routes in the browser: http://localhost:4200/ ...

Issue in Angular: Attempting to access properties of undefined (specifically 'CustomHeaderComponent')

I have encountered a persistent error message while working on a new component for my project. Despite double-checking the injection code and ensuring that the module and component export logic are correct, I am unable to pinpoint the issue. custom-header ...

Strategies for modifying the title attribute within an <a> tag upon Angular click event

I am attempting to dynamically change the title attribute within an anchor tag upon clicking it. The goal is for the title attribute to toggle between two states each time it is clicked. Currently, I am able to change the title attribute successfully upon ...

Retrieving data from Redis cache may not always yield the exact same data

I have been working on creating a small Express app that retrieves data from a PostgreSQL query and caches the result in a Redis database. Here is my approach: app.get('/query_tile/:z/:x/:y', async (req: Request, res: Response) => { const ...

Encountering an issue with applying D3 fill to a horizontal stacked bar chart in Angular using TypeScript. When using .attr("fill", ..) in VSC, an error stating "No overload matches this call" is displayed

My goal is to create a stacked horizontal bar chart in d3, and I've been following the code example provided here. To showcase my progress so far, I have set up a minimal reproduction on stackBlitz which can be found here. While there are no errors ...

Set every attribute inside a Typescript interface as non-mandatory

I have defined an interface within my software: interface Asset { id: string; internal_id: string; usage: number; } This interface is a component of another interface named Post: interface Post { asset: Asset; } In addition, there is an interfa ...

Retrieving Data from Angular Component within a Directive

Currently, I am in the process of creating an "autocomplete" directive for a project. The aim is to have the directive query the API and present a list of results for selection. A component with a modal containing a simple input box has been set up. The ob ...

D3-cloud creates a beautiful mesh of overlapping words

I am encountering an issue while trying to create a keyword cloud using d3 and d3-cloud. The problem I am facing is that the words in the cloud are overlapping, and I cannot figure out the exact reason behind it. I suspect it might be related to the fontSi ...

The assignment of `accessToken` is restricted in Mapbox-gl's typing

I'm currently utilizing the mapbox-gl library in conjunction with TypeScript. Moreover, I have successfully installed its type definitions that are sourced from the community using @types/mapbox-gl. However, when attempting to import and assign an acc ...

I'm curious if it's possible to set up both Tailwind CSS and TypeScript in Next.js during the initialization process

When using the command npx create-next-app -e with-tailwindcss my-project, it appears that only Tailwind is configured. npx create-next-app -ts If you use the above command, only TypeScript will be configured. However, running npx create-next-app -e with ...