Factory Pattern Utilizing Enum for Field Population

Struggling to find a solution for setting default values for instances created by the factory method createLetterMap...

I don't think the problem lies in 'How to loop over enums' because it seems impossible due to types not being available at run-time. However, I might be missing something regarding string enums?


enum Letter{
    A = "A",
    B = "B",
    E = "E",
    I = "I",

}
type Vowl = Letter.A | Letter.E | Letter.I

class LMAP{
    test(){}
}
function createLetterMap<T extends Letter>(){
    let lmapExtended = new LMAP() as LMAP & {
        [key in T]?:string
    };

    // ?? create default values for keys somehow ??

    return lmapExtended;
}

let vowlMap = createLetterMap<Vowl>()

vowlMap[Letter.E] = "Eat" // OK
// vowlMap[Letter.B] = "Bat" // Error! Good!

let defaultVal = vowlMap[Letter.A]; // undefined. Would like it to be populated.

My goal is to use unions of string enums to generate objects with keys that can be used similar to this scenario:

fn(v:Vowl){

  ...

  letterMap[v].someVowlReleatedWork()

  ...

}

While using Maps is a working alternative, I feel there could be a cleaner way if types are specified correctly...

The current workaround involves creating an array of enums included in the union type and utilizing both the union and the array in the factory method, which doesn't seem ideal:

...

let Vowls = [Letter.A,... ]
createLetterMap<Vowl>(Vowls)

Answer №1

Using type definitions as values is not possible because they are removed at runtime - for(let key of keyof Vowl would not function.

To work around this limitation, you could create the map as a class or utilize a custom transformer like ts-transformer-keys to extract keys at runtime.


enum Letter {
  A = "A",
  B = "B",
  E = "E",
  I = "I",

}
type Vowl = Letter.A | Letter.E | Letter.I

interface IMapper<T> { operation(value: T): void }

class VowlMapper implements IMapper<Vowl> {
  operation(vowel: Vowl) {
    console.log("VowlMapper: " + vowel);
  }
}

class LetterMap<T extends Letter> {

  private map: IMapper<T> & { [key in T]?: string };

  constructor(map: IMapper<T>) {
    this.map = map;
  }

  get(letter: T) {
    return this.map[letter] ?? letter;
  }

  set(letter: T, value: any) {
    this.map[letter] = value;
  }

  operation(value: T) {
    return this.map.operation(value);
  }

}

let vowlMap = new LetterMap(new VowlMapper())

vowlMap.set(Letter.E, "Eat");
// vowlMap.set(Letter.B, "Bat"); // Error! Good!

console.log(vowlMap.get(Letter.A))  // A
//console.log(vowlMap.get(Letter.B))  // error
console.log(vowlMap.get(Letter.E))  // Eat

vowlMap.operation(Letter.A); // VowlMapper: A 

Test out the playground example.

Answer №2

An interesting method I've discovered (especially in terms of utilizing the output) involves using a const array of enums. The process is somewhat complex; there is a risk of accidentally overwriting LMAP fields with the input enum collection...

When setting default values, it appears necessary to cast to any, although the reason behind this action is unclear. Given that the input array must consist of Letter enums, the casting doesn't concern me much, but it does seem a bit peculiar...

enum Letter{
    A = "A",
    B = "B",
    E = "E",
    I = "I"    
}

// Using a const array for subsets allows for an iterable structure and a basis for type derivation
const Vowls = <const> [
    Letter.A,
    Letter.E,
    Letter.I
];

// Define the base class to 'extend'
class LMAP{};
// Function that accepts a const array of the original enum type for type information and iteration 
function createLMAP<T extends Readonly<Array<Letter>>,T2>(letterArray:T,defaultVal:T2){    
    let n = new LMAP() as LMAP & {[key in typeof letterArray[number]]:T2};
    letterArray.forEach(key=>{                
        n[key as T[number]] = defaultVal as any; // Unsure about the need to cast to any here...
    });
    return n;
}

// Voila!
let vm = createLMAP(Vowls,0);

// Testing phase
Vowls.forEach(v=>{
    vm[v] += 1;
})
vm[Letter.A] = 1;
vm[Letter.B] = 1; // Error caused by lack of implicit any

// Derive a union type for future use...
type Vowl = typeof Vowls[number]; 
// Example usage:
function work(v:Vowl){
    vm[v] = 1;
}
work(Letter.A)
work(Letter.B) // Error!

While I'm not confirming this as the definitive answer, it's the path I personally prefer.

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

Combine two elements in an array

I am faced with a challenge in binding values from an Array. My goal is to display two values in a row, then the next two values in the following row, and so on. Unfortunately, I have been unable to achieve this using *ngFor. Any assistance would be greatl ...

typescript function intersection types

Encountering challenges with TypeScript, I came across the following simple example: type g = 1 & 2 // never type h = ((x: 1) => 0) & ((x: 2) => 0) // why h not never type i = ((x: 1 & 2) => 0)// why x not never The puzzling part is w ...

Using Angular 6 to Format Dates

Can anyone provide instructions on how to achieve the following format in Angular? Expected: 20JAN2019 Currently, with the default Angular pipe, I am getting: 20/01/2019 when using {{slotEndDate | date:'dd/MM/yyyy'}} Do I need to write a ...

Seeking a quick conversion method for transforming x or x[] into x[] in a single line of code

Is there a concise TypeScript one-liner that can replace the arrayOrMemberToArray function below? function arrayOrMemberToArray<T>(input: T | T[]): T[] { if(Arrary.isArray(input)) return input return [input] } Trying to cram this logic into a te ...

tsc is not recognizing the configurations in my tsconfig.json file

Running tsc in my project's directory is causing an error to be outputted (as shown below). This is my first attempt at using TypeScript and Node.js. Please consider me as a complete beginner. Operating system: Ubuntu 15.10 64bits NPM version: 2.4. ...

What sets apart the two methods of defining an event in a React component?

Can you explain the nuances between these two approaches to declaring events in a React component? Is it merely a matter of personal preference, or are there more subtle distinctions between them? interface PropsX { onClick: () => void; } const But ...

Solving issues with malfunctioning Angular Materials

I'm facing an issue with using angular materials in my angular application. No matter what I try, they just don't seem to work. After researching the problem online, I came across many similar cases where the solution was to "import the ...

Typescript headaches: Conflicting property types with restrictions

Currently, I am in the process of familiarizing myself with Typescript through its application in a validation library that I am constructing. types.ts export type Value = string | boolean | number | null | undefined; export type ExceptionResult = { _ ...

Issue with accessing custom method in subclass in Typescript

Recently delving into TypeScript, I decided to subclass Error and add a new method called getCode() in my MyError class. export class MyError extends Error { public code: number; constructor(code: number, message?: string) { super(mes ...

Create a chronological timeline based on data from a JSON object

Here is a JSON object generated by the backend: { "step1": { "approved": true, "approvalTime": "10-11-2021", "title": "title 1", "description": "description 1" ...

Troubles encountered when trying to execute mocha within Firebase functions

My latest project involved developing a Node/Typescript app that interacted with data from Firebase Cloud Firestore. The app performed flawlessly, and I conducted endpoint testing using simple mocha commands on the generated .js file. Below is an example o ...

What is the best way to send two separate properties to the selector function?

My selector relies on another one, requiring the userId to function properly. Now, I want to enhance the selector to also accept a property named "userFriend". However, there seems to be an issue with passing this new parameter, as it only recognizes the ...

Express throwing module errors

I encountered an issue while attempting to expose a REST service in an electron app using expressJS. Following a tutorial, I added express and @types/express to the project. However, when trying to implement a "get" method and running the build with ng bui ...

Generic type array does not display property

I feel like I must be overlooking something. It seems too straightforward to be causing issues for me. Database.ts export class Database { id: number; } search-input.ts import { Database } from './../resources/database'; import { Inje ...

Unable to locate the next/google/font module in my Typescript project

Issue With Import Syntax for Font Types The documentation here provides an example: import { <font-name> } from 'next/google/font'; This code compiles successfully, but throws a "module not found" error at runtime. However, in this disc ...

Is there a method in TypeScript to make an enum more dynamic by parameterizing it?

I've defined this enum in our codebase. enum EventDesc { EVENT1 = 'event 1', EVENT2 = 'event 2', EVENT3 = 'event 3' } The backend has EVENT1, EVENT2, EVENT3 as event types. On the UI, we display event 1, event 2, a ...

"Prisma vs. Supabase: A Comparison of Image Uploading

I am encountering an issue with adding image URLs to the Prisma database. I have successfully uploaded multiple images from an input file to Supabase storage, but when I try to add their URLs to the database, I receive an error regarding property compatibi ...

Comparison between a Typescript optional field and a field that has the potential to be undefined

Can you clarify the contrast between an optional field and a T | undefined field? export interface Example { property1: string | undefined property2?: string } ...

Creating synchronous behavior using promises in Javascript

Currently, I am working with Ionic2/Typescript and facing an issue regarding synchronization of two Promises. I need both Promises to complete before proceeding further in a synchronous manner. To achieve this, I have placed the calls to these functions in ...

Find the appropriate return type for a TypeScript function based on its argument

Is it feasible in TypeScript to infer the return type of a function based on its arguments? This feature would be beneficial when extracting specific properties from, for example, a database query. Here is an illustration (https://repl.it/repls/Irresponsi ...