110

I've searched around but can't seem to find an answer for this, hopefully you can help.

How can I add an enum to Image? This is what I would like ideally but I get an error.

declare module 'Lib' {
  export module Graphics {
    export class Image {
      enum State {}

      static STATE_IDLE: State;
      static STATE_LOADING: State;
      static STATE_READY: State;
      static STATE_ERROR: State;
      constructor();
    }
  }
}

If I move State into the Graphics module it works but now State belongs to Graphics, which is incorrect. It needs to be part of Image.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Lewis Peel
  • 1,319
  • 2
  • 8
  • 15

7 Answers7

92

I think the following is an improvement on KoenT's solution:

export class Image
{
    constructor ()
    {
        this.state = Image.State.Idle;
    }

    state: Image.State;
}

export namespace Image
{
    export enum State
    {
        Idle,
        Loading,
        Ready,
        Error
    }
}

The advantage being that you can leverage named imports:

import {Image} from './image';
let img = new Image()
img.state = Image.State.Error
NSjonas
  • 10,693
  • 9
  • 66
  • 92
  • 15
    This technique is also described in the official reference under [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-namespaces-with-classes). To use this technique with `export default`, one would remove the `export` keyword from `class` and `namespace` and add the line `export default Image;` after the closing bracket of the `namespace`. – zett42 Dec 29 '19 at 13:59
  • Thanks for that answer! I did not know about this Declaration merging yet and.. wow, that's powerfull! – Janis Jansen Apr 29 '20 at 11:19
  • It's not working anymore. Error: 'namespace' and 'module' are disallowed (no-namespace). Check https://www.typescriptlang.org/docs/handbook/declaration-merging.html#disallowed-merges – Shadoweb Sep 13 '20 at 11:14
  • @Shadoweb this definitely still works... The "no-namespace" error is a ts-lint configuration (and a controversial one), NOT a typescript error. See https://palantir.github.io/tslint/rules/no-namespace/ and https://github.com/microsoft/TypeScript/issues/30994 – NSjonas Sep 13 '20 at 17:56
  • 1
    not working, you cannot use the enums inside the class itself, what a load of.... – phil123456 Oct 09 '20 at 00:51
  • @phil123456 Sounds like you are confused. It definitely works and you can use the enum inside the class (as clearly illustrated in the example itself) https://stackblitz.com/edit/typescript-playground-sandbox-7v4d5j – NSjonas Oct 20 '20 at 21:52
  • @NSjonas it constructor... yes... but it doesn't work if I want to define some properties – noisy Dec 10 '20 at 19:04
  • @noisy can you elaborate? Maybe provide a link to what you are trying to do? – NSjonas Dec 10 '20 at 19:08
  • Not working for me, typescript compiles without error, but at runtime the enum is undefined – KVM Apr 24 '22 at 18:15
61

Here's my solution.

program.ts:

enum Status {
    Deleting,
    Editing,
    Existing,
    New
}

export class Program {
    static readonly Status = Status;
    readonly Status = Program.Status;

    title: string;

    status: Status;

    constructor(init?: Partial<Program>) {
        Object.assign(this, init);
    }
}

Usage:

let program = new Program({ title: `some title` });

program.status = Program.Status.New;

or

program.status = program.Status.New;

Added benefit for Angular 2+ users: this can be used in templates

<div *ngIf="program.status === program.Status.New">
  Only display if status of the program is New
</div>
Maz T
  • 1,184
  • 13
  • 18
  • 3
    Nice approach but what if I need to declare a variable of enum's type? `enumVar: Program.Status;` not working – Dimanoid Jul 31 '18 at 15:24
  • 1
    This should be the accepted solution, the other ones produce lint warnings (https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/no-namespace.md) – graup Mar 12 '19 at 06:39
  • Doesn't work with Angular unless you remove the `static` since templates can't use static properties of their component. – Hutch Moore Sep 12 '20 at 13:17
  • 1
    Hence `readonly Status = Program.Status;` just under `static`. – Maz T Nov 09 '20 at 11:42
  • @Dimanoid, to achieve what you want you'd need to add 'export' keyword to enum Status {...} declaration, then you can write 'enumVar: Status' – Maz T Apr 27 '22 at 16:27
  • @MazT what if I want to do something like that: `event = new EventEmitter();`? – Paul K. Feb 08 '23 at 10:31
39

I also bumped into this problem recently. This is what I am currently using as a solution:

// File: Image.ts

class Image
{
    constructor()
    {
        this.state = Image.State.Idle;
    }

    state: Image.State;
}

module Image
{
    export enum State
    {
        Idle,
        Loading,
        Ready,
        Error
    }
}

export = Image;

Then in the place where I'm using the class and its enum:

import Image = require("Image");

let state = Image.State.Idle;
let image = new Image();
state = image.state;

This seems to work fine (even though I don't consider it as the expected way to do this kind of thing).

Hopefully there will be a way in TypeScript to do it this way:

class Image
{
    enum State
    {
        Idle,
        Loading,
        Ready,
        Error
    }

    constructor()
    {
        this.state = State.Idle;
    }

    state: State;
}

export = Image;
KoenT
  • 491
  • 5
  • 7
3

I think I may have found a solution...whether it's valid TypeScript I don't know but it works and doesn't cause any compile errors. It's a combination of the above answers.

declare module 'Lib' {

  module Graphics {

    module Image {
      enum State { }
      var STATE_IDLE: State;
      var STATE_LOADING: State;
      var STATE_READY: State;
      var STATE_ERROR: State;
    }

    class Image {
      constructor();
    }

  }

}

Can anyone spot any potential issues with this that I haven't noticed?

Lewis Peel
  • 1,319
  • 2
  • 8
  • 15
  • I could probably even remove the values from inside the enums too without affecting anything. – Lewis Peel Apr 24 '15 at 15:40
  • 1
    Now you can access it two ways: `Image.State.STATE_IDLE` and `Image.STATE_IDLE`. It's a little difficult for someone looking at your definition to figure out what they're supposed to use. – David Sherret Apr 24 '15 at 15:48
  • True. I've edited the answer and removed the values from inside the enum...which makes more sense. This actually looks like what threejs has in their definition file. – Lewis Peel Apr 24 '15 at 15:55
3

I think that this stuff with module augmentation is a very hacky and non-intuitive way* of doing things, so consider this:

export module Graphics
{
    enum State
    {
        STATE_IDLE,
        STATE_LOADING,
        STATE_READY,
        STATE_ERROR
    }

    export class Image
    {
        constructor() { }
        public static readonly State = State;
    }
}

//...

let imgState = Graphics.Image.State.STATE_ERROR;

That is, just declare the enum in the scope of the class that you want to add it to without exporting it, then expose it through a member of the class.

* Which in regards of structuring and organization of code is BAD, even if it technically works.

Update

declare module Lib
{
    enum State
    {
        STATE_IDLE,
        STATE_LOADING,
        STATE_READY,
        STATE_ERROR
    }

    class ImageClass
    {
        constructor();
        public Prop: any;
    }

    export interface Graphics
    {
        Image: typeof State & ImageClass & (new () => typeof State & ImageClass);
    }
}

declare var Graphics: Lib.Graphics;

Then you get typing like:

var someEnum = Graphics.Image.STATE_ERROR;
var image = new Graphics.Image();
var anotherEnum = image.STATE_IDLE;
Alex
  • 14,104
  • 11
  • 54
  • 77
  • I appreciate that but I was trying to write a declaration for for an existing library, something out of my control so 'Graphics.Image.State.STATE_ERROR' would not work for me - it has to be 'Graphics.Image.STATE_ERROR' – Lewis Peel Jan 04 '17 at 10:18
  • @LewisPeel Ah, sorry. I've updated the answer, would that give you any value? – Alex Jan 04 '17 at 12:18
1

I'm not sure what you intend to do, but I would have expected that you would want an enum to represent the possible state values, and then a state member on the image to indicate the current state of the image.

declare module 'Lib' {
    export module Graphics {

        enum State {
            STATE_IDLE,
            STATE_LOADING,
            STATE_READY,
            STATE_ERROR
        }

        export class Image {
            public state: State;

            constructor();
        }

    }
}

It sounds like you want to declare a class that has enum-like members, rather than declare an enum within a class. i.e:

declare module 'Lib' {

    export module Graphics {

        export class Image {
            static STATE_IDLE: number;
            static STATE_LOADING: number;
            static STATE_READY: number;
            static STATE_ERROR: number;

            constructor();
        }
    }
}
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • Thanks for your reply Steve. The way I access any of the state values is like so; Lib.Graphics.Image.STATE_READY rather than a public property - not my decision but that's how the library is. Is there a way to do static enums on classes? – Lewis Peel Apr 24 '15 at 13:37
  • In that case, I think you just want to declare the enum-like members on the `Image` class (which will be `number` types, just like enums are under the hood). – Fenton Apr 24 '15 at 13:41
  • I see. Although I have a function that should only accept idle, loading, ready or error - if it's not one of those it shouldn't be allowed. If I had them declared as numbers people could send "100" and it would be valid right? – Lewis Peel Apr 24 '15 at 13:47
0

You could create a module and class with the same name. It might also help to rename your enum so that you don't have to say State twice:

declare module 'Lib' {
    export module Graphics {
        export class Image {
            constructor();
        }

        export module Image {
            export enum State {
                Idle,
                Loading,
                Ready,
                Error
            }
        }
    }
}
David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • Same applies for [creating a class within a class](http://stackoverflow.com/q/28717059/188246). – David Sherret Apr 24 '15 at 14:11
  • That kinda works but it means I have to write "Lib.Graphics.Image.State.STATE_IDLE" instead of "Lib.Graphics.Image.STATE_IDLE" – Lewis Peel Apr 24 '15 at 14:20
  • 2
    @LewisPeel then what you're looking for is not an enum in a class, but static number properties on a class. Use Steve's answer, but explicitly give each of those properties a value... ex. `static STATE_IDLE: number = 0; static STATE_LOADING: number = 1;` etc... – David Sherret Apr 24 '15 at 14:22
  • I think that's my only option. It's a shame though because like I said, you could use 100 instead of Image.STATE_LOADING and the compiler wouldn't flag it up...but when the code runs it'll throw an error. – Lewis Peel Apr 24 '15 at 14:37
  • Oh, I forgot this was a definition file. You can't assign the values to those members, but just make sure you do that in the corresponding javascript file (I meant to originally say members and and not properties too) – David Sherret Apr 24 '15 at 15:35
  • I think I might have found a solution. http://stackoverflow.com/a/29851144/488653 – Lewis Peel Apr 24 '15 at 15:38
  • @LewisPeel I updated the answer so you don't have to write state twice. I think it makes more sense to do this than anything (as long as you have control over the original javascript). – David Sherret Apr 24 '15 at 15:46