Creating a custom enum using generics in TypeScript can be excessively intricate

Is there a simpler way to streamline this code? It feels very repetitive and not quite right...

const FolderVisibility = new Enum<{
    PUBLIC: 'public',
    PRIVATE: 'private'
}>({
    PUBLIC: 'public',
    PRIVATE: 'private'
}) as Enum<{
    PUBLIC: 'public',
    PRIVATE: 'private'
}> & {
    PUBLIC: 'public',
    PRIVATE: 'private'
}

I'd like the IDE to indicate that the

FolderVisibility.PUBLIC == 'public'
since the parameter is readonly anyhow.

Here is the Enum class, which has its own properties and a function

export default class Enum<T extends { [index: string]: string }> {
    private readonly map: T;
    public  readonly values: (T[keyof T])[];

    constructor(enums: T) {
        Object.defineProperty(this, 'map', { value: {} });

        for (let prop in enums) {
            if (enums.hasOwnProperty(prop)) {
                const value = enums[prop]
                if(typeof value != 'string'){
                    throw new EnumError(value)
                }
                this.map[prop] = value
                Object.defineProperty(this, prop, { value });
            }
        }

        Object.defineProperty(this, 'values', { value: Object.values(this.map) });
    }

    isValid(text: any) {
        if (!text) return true
        return this.values.includes(text)
    }
}

The idea is that by copying the object used in the constructor 4 times, it should indicate that FolderVisibility.values is of type 'public' | 'private'

PS: I tried the following approach, but it returned string for FolderVisibility.values. It also feels quite lengthy. const data = { PUBLIC: 'public', PRIVATE: 'private' }

const FolderVisibility = new Enum<typeof data>(data) as Enum<typeof data> & typeof data

Answer №1

The issue with object literals and literal types lies in the inability of the compiler to automatically infer a literal type for an object literal property. This is where specifying the generic type argument becomes crucial.

One aspect of your method that could be streamlined is the casting process after the enum. Rather than using a constructor, opting for a simple function provides more flexibility in terms of return options:

function CustomEnum<T extends{ [P in keyof T]: string }>(enums: T) {

    let map : { [index: string]: string } = {}

    for (let prop in enums) {
        if (enums.hasOwnProperty(prop)) {
            const value = enums[prop]
            if(typeof value != 'string'){
                throw new EnumError(value)
            }
            map[prop] = value;
        }
    }
    let result = Object.assign({}, enums , {
        values: Object.values(map),
        isValid(text: string) {
            if (!text) return true
            return this.values.includes(text)
        }
    });
    return Object.freeze(result);
}
const FolderVisibility = CustomEnum<{
    PUBLIC: 'public',
    PRIVATE: 'private'
}>({
    PUBLIC: 'public',
    PRIVATE: 'private'
});
console.log(FolderVisibility.isValid("")) // Works
console.log(FolderVisibility.PRIVATE === "private" ) // And const fields of string literal type

The same function can also be used to enhance an existing enum with minimal explicit typing requirements:

enum FolderVisibilityProto {
    PUBLIC ='public',
    PRIVATE=  'private'
}
const FolderVisibility = CustomEnum(FolderVisibilityProto);

Alternatively, the CustomEnum function can be modified to accept a callback that generates the enum internally, ensuring no external access to the unaugmented enum:

function CustomEnum<T extends{ [P in keyof T]: string }>(enumsCreator: () => T) {
    let enums = enumsCreator();
    …
}

const FolderVisibility = CustomEnum(()=> 
{
    enum FolderVisibility {
        PUBLIC ='public',
        PRIVATE=  'private'
    }
    return FolderVisibility;
});

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

Tips for efficiently displaying and handling vast quantities of information within an Angular 2 table

I am currently working on the development of an Angular 2 application that requires displaying a large amount of data in a scrollable table. The data is stored in an array within my component class: export class AppComponent { clients: any[] = []; ...

"Null value is no longer associated with the object property once it has

What causes the type of y to change to string only after the destruction of the object? const obj: { x: string; y: string | null } = {} as any const { x, y } = obj // y is string now ...

What is the best way to retrieve a specific property from an array of objects in Angular 6 using typescript?

I am currently working on a budgeting application that incorporates an array of expenses with a price property. Each expense is defined within an Expense model in Typescript. While I can easily access the price property using ngFor loop in HTML, I'm c ...

A step-by-step guide for setting up MongoDB on a "Blank Node.js Console Application" project in VS2015 with TypeScript

Here is my process: First, I installed MongoDB Next, I opened Visual Studio 2015 Then, I created a new project by going to "File" -> "New" -> "Project" -> "TypeScript" -> "Blank Node.js Console Application" After that, I opened the project fo ...

What methods can be used to identify the generic type within a class structure?

Suppose I have a class like this: class Foo<T>{} How can I determine the type of the instance of the class within a method? In other words, something along the lines of: public barbaz(){ // This approach does not function if(typeof(<T>) == ...

How to dynamically change the placeholder in an Angular 2 Material Input field

Is it possible to dynamically change the text of an input placeholder? I am able to update the text using console.log, but the interface does not reflect the change. How can I make the interface recognize the updated placeholder text? document.getElemen ...

Infer the types and flatten arrays within arrays

I am currently working on creating a custom function in typescript that can flatten nested arrays efficiently. My current implementation is as follows: function flattenArrayByKey<T, TProp extends keyof T>(array: T[], prop: TProp): T[TProp] { re ...

Divide the string into several segments according to its position value

Here is a piece of text that I would like to divide into multiple sections, determined by the offset and length. If you have any questions or comments and would like to get in touch with ABC, please go to our customer support page. Below is a function ...

RxJs will only consider the initial occurrence of a specific type of value and ignore any subsequent occurrences until a different type of value is encountered

I'm faced with a situation where I need to extract the first occurrence of a specific value type, followed by the next unique value of a different type. Let's break it down with an example: of(1,1,1,1,2,3,4) .pipe( // some operators ) .subsc ...

I require the ability to modify cellEditor parameters in real-time

How can a value be passed to cellEditorParams after the user double clicks on a grid row? The application triggers a service call on row click and the response needs to be sent to cellEditorParams. ...

Can a TypeScript generic version of the Y-combinator be successfully executed?

Here is an interesting JavaScript implementation of the Y-combinator: const Y = g => (x => g(x(x)))(x => g(x(x))) //or const Y = f => { const g = x => f(x(x)) return g(g) } I've been thinking, could it be possible to create a TypeS ...

Accessing element from view within controller in Ionic version 3.5

I am currently working on a project in Ionic 3.5, where I need to implement a feature that automatically loads an image "ad" after the page finishes loading. Right now, clicking a button successfully displays the image. However, I want this functionality ...

How to convert typescript path aliases into relative paths for NPM deployment?

I am currently working on a typescript project that utilizes paths for imports. For instance: "paths": { "@example/*": ["./src/*"], } This allows the project to import files directly using statements like: import { foo } from "@example/boo/foo"; Whe ...

Unable to bring in a personalized npm package using its package title

Currently, I am loosely following a tutorial on creating an angular npm package named customlib. This package is intended for managing dependencies across my projects without the need to make them public on npm. The tutorial can be found here. However, I ...

Inversify is a proven method for effectively injecting dependencies into a multitude of domain classes

Struggling to navigate dependencies and injections in a TypeScript-built rest web service without relying heavily on inversify for my domain classes, in line with the dependency inversion principle. Here's an overview of the project structure: core/ ...

Testing with mount in React Enzyme, the setState method does not function correctly

I've been experimenting with testing this code block in my React App using Jest and Enzyme: openDeleteUserModal = ({ row }: { row: IUser | null }): any => ( event: React.SyntheticEvent ): void => { if (event) event.preventDefault(); ...

What is the best way to incorporate Ekko Lightbox into an Angular 7 Project?

I'm facing an issue while implementing Ekko lightbox in my angular project. Specifically, I want to use it in a certain component but I am unsure about how to import the necessary files into that component. After installing Ekko via NPM, I found all ...

Is there a way to verify whether a key within an Interface corresponds to a class that is a subclass of a specific Parent

Is there a method in typescript to ensure that a property in an interface must be of type "child subclass C, which extends class P"? example.ts import { P } from '/path/to/types' class C extends P { ... } types.ts // `C` cannot be accessed ...

Responsive Container MUI containing a grid

I am attempting to replicate the functionality seen on YouTube's website, where they dynamically adjust the grid layout based on the container size when a Drawer is opened or closed. Essentially, it seems that YT adjusts the grid count based on the c ...

What is the best way to design a basic server component that has the ability to retrieve data in NextJS 13?

Exploring the world of NextJS, I am currently utilizing NextJS 13 with the new app directory instead of the traditional pages directory structure. Despite trying various methods to fetch data, none seem to be working as expected. The process should be stra ...