Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending an imported enum in another file and using it

I'm trying to extend an imported enum in a different file, and using that extended enum in another different file.

The general case

base.enum.ts

export enum MyEnum {
    a = "Foo"
}

extended.enum.ts

import { MyEnum } from './base.enum';

declare module './base.enum' {
    export enum MyEnum {
        b = "Bar"
    }
}

Using in index.ts

import { MyEnum } from './base.enum';
import './extended.enum'; // side-effects import (no usage of export)

console.log(MyEnum.a); // prints "Foo" as expected
console.log(MyEnum.b); // prints undefined, instead of the expected "Bar"

(I'm doing this in TypeScript 2.4.2 which supports string value enums)

I've used this and this SO questions as reference and read the following TypeScript issue in GitHub and still couldn't find a solution to my problem.


Example of usage which resembles mine

Base enum AnimalTypes in animals/base/animal-types.enum.ts:

export enum AnimalTypes { }

Base interface Animal in animals/base/animal.ts:

import { AnimalTypes } from './animal-types';
export interface Animal {
    type: AnimalTypes;
}

animals/base/index.ts:

export * from './animal-types.enum';
export * from './animal';

Extended enum AnimalTypes in animals/animal-types.enum.ts:

import { AnimalTypes } from './base/';
declare module './base/animal-types.enum' {
    export enum AnimalTypes {
        Cat = "cat",
        Dog = "dog"/*,
        ...*/
    }
}

Concrete class Cat in animals/cat.ts:

import { Animal, AnimalTypes } from './base/';
import './animal-types.enum';
export class Cat implements Animal {
    public type: AnimalTypes = AnimalTypes.Cat; // Usage of AnimalTypes with extended value
}

Concrete class Dog in animals/dog.ts:

import { Animal, AnimalTypes } from './base/';
import './animal-types.enum';
export class Dog implements Animal {
    public type: AnimalTypes = AnimalTypes.Dog; // Usage of AnimalTypes with extended value
}

animals/index.ts:

export * from './cat';
export * from './dog';
//...

Final usage in animals/animals-manager.ts:

import { Animal, AnimalTypes} from './base/';
import { Cat, Dog/*, ...*/ } from '.';
import './animal-types'; // side-effects import (no usage of export)

export class AnimalsManager {
    public animals: { [animal: string]: Animal } = {}; // Animal dictionary (I would use AnimalTypes as key type but that isn't supported yet as far as I know).
    constructor() {
        this.animals[AnimalTypes.Cat] = new Cat();
        this.animals[AnimalTypes.Dog] = new Dog();
    }
    //...
}

When trying to use AnimalManager's animals[AnimalTypes.Cat] I'll get the value of animals[AnimalTypes.Dog] because both AnimalTypes.Cat and AnimalTypes.Dog return undefined (which means animals[AnimalTypes.Cat] was overridden by setting animals[AnimalTypes.Dog]).


So, is there currently a way to extend imported enums in TypeScript as described above or will I have to hack my way around to get a custom enum which supports such extending? Thanks.

like image 683
AnonAppDev Avatar asked Oct 15 '17 17:10

AnonAppDev


People also ask

Can you extend enums?

The short answer is no, you can't extend enums because TypeScript offers no language feature to extend them. However, there are workarounds you can utilize to achieve what inheritance would.

Can you extend enums C#?

Since it's not a class, it cannot be extended and its defined values are limited to a small set of primitive types ( byte , sbyte , short , ushort , int , uint , long , ulong ). For instance, sometimes it is very convenient to get the list of enum values and display it in a combobox.


1 Answers

TypeScript allows to extend only type information in declare module, no code is ever executed from these declarations. In fact, having code inside declare module is in general disallowed, for example placing a function with a body there will give An implementation cannot be declared in ambient contexts error.

So you declared additional member for MyEnum enum type, but that member is not initialized anywhere, so its value is undefined at runtime. To fix that, you can initialize it yourself:

extended.enum.ts

import { MyEnum } from './base.enum';

declare module './base.enum' {
    export enum MyEnum {
        b = "Bar"
    }
}

const myEnumValues = MyEnum as any;
myEnumValues["b"] = "Bar";

You have to cast MyEnum to any first, because direct initialization is not allowed:

MyEnum["b"] = "Bar";

does not compile with error

error TS2540:Cannot assign to 'b' because it is a constant or a read-only property.
like image 131
artem Avatar answered Oct 29 '22 22:10

artem