206

The Typescript enum seems a natural match with Angular2's ngSwitch directive. But when I try to use an enum in my component's template, I get "Cannot read property 'xxx' of undefined in ...". How can I use enum values in my component template?

Please note that this is different from how to create html select options based upon ALL of the values of an enum (ngFor). This question is about ngSwitch based upon a particular value of an enum. Although the same approach of creating an class-internal reference to the enum appears.

Carl G
  • 17,394
  • 14
  • 91
  • 115
  • Possible duplicate of [Select based on enum in Angular2](http://stackoverflow.com/questions/35750059/select-based-on-enum-in-angular2) – Günter Zöchbauer Mar 07 '16 at 06:02
  • 2
    I don't think that these questions are duplicates; the other one is asking how to create HTML select options based upon ALL of the values of an enum (ngFor), whereas this one is about ngSwitch based upon a particular value of an enum. Although the same approach of creating an class-internal reference to the enum appears. Thank you for pointing out the similarity. – Carl G Mar 07 '16 at 18:20

10 Answers10

211

You can create a reference to the enum in your component class (I just changed the initial character to be lower-case) and then use that reference from the template (plunker):

import {Component} from 'angular2/core';

enum CellType {Text, Placeholder}
class Cell {
  constructor(public text: string, public type: CellType) {}
}
@Component({
  selector: 'my-app',
  template: `
    <div [ngSwitch]="cell.type">
      <div *ngSwitchCase="cellType.Text">
        {{cell.text}}
      </div>
      <div *ngSwitchCase="cellType.Placeholder">
        Placeholder
      </div>
    </div>
    <button (click)="setType(cellType.Text)">Text</button>
    <button (click)="setType(cellType.Placeholder)">Placeholder</button>
  `,
})
export default class AppComponent {

  // Store a reference to the enum
  cellType = CellType;
  public cell: Cell;

  constructor() {
    this.cell = new Cell("Hello", CellType.Text)
  }

  setType(type: CellType) {
    this.cell.type = type;
  }
}
Carl G
  • 17,394
  • 14
  • 91
  • 115
111

This's simple and works like a charm :) just declare your enum like this and you can use it on HTML template

statusEnum: typeof StatusEnum = StatusEnum;
SwissCodeMen
  • 4,222
  • 8
  • 24
  • 34
Aymen Boumaiza
  • 1,339
  • 1
  • 9
  • 9
91

You can create a custom decorator to add to your component to make it aware of enums.

myenum.enum.ts:

export enum MyEnum {
    FirstValue,
    SecondValue
}

myenumaware.decorator.ts

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

export function MyEnumAware(constructor: Function) {
    constructor.prototype.MyEnum = MyEnum;
}

enum-aware.component.ts

import { Component } from '@angular2/core';
import { MyEnum } from './myenum.enum';
import { MyEnumAware } from './myenumaware.decorator';

@Component({
  selector: 'enum-aware',
  template: `
    <div [ngSwitch]="myEnumValue">
      <div *ngSwitchCase="MyEnum.FirstValue">
        First Value
      </div>
      <div *ngSwitchCase="MyEnum.SecondValue">
        Second Value
      </div>
    </div>
    <button (click)="toggleValue()">Toggle Value</button>
  `,
})
@MyEnumAware // <---------------!!!
export default class EnumAwareComponent {
  myEnumValue: MyEnum = MyEnum.FirstValue;

  toggleValue() {
    this.myEnumValue = this.myEnumValue === MyEnum.FirstValue
        ? MyEnum.SecondValue : MyEnum.FirstValue;
  }
}
Eric Lease
  • 4,114
  • 1
  • 29
  • 45
  • 7
    Has anyone had success using this method with the AoT compiler? – Danny Mar 06 '17 at 20:15
  • 1
    @Danny Yes, compiled with `ng build --prod --aot` using angular/cli 1.0.0 with Angular 4.0.1 works fine for me. – Glenn Apr 20 '17 at 03:58
  • Wonderful solution. I hate to explicitly declare a component variable to access Enum in HTML – TechCrunch Jun 07 '17 at 21:15
  • As beautiful as this is, I have been unable to get this working in my setup. Without `exports **default** class`, `ng build` can not compile it saying that it doesn't know what that enum is, when I add `default`, then it says that my component doesn't exist... sad beans... – Serj Sagan Jun 19 '17 at 21:16
  • I *almost* understand how this works, but can someone explain exactly how it does. Or if I look up 'angular decorators' will it be obvious? – Simon_Weaver Jun 20 '17 at 20:52
  • 2
    @Simon_Weaver **decorators** are essentially functions that take a function as a parameter and extend the behavior of that function. In the case of ES6/7, we're dealing with the extension/annotation of classes. Here's a [high level article about how they work](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841). The [proposal for implementation in ES7](https://tc39.github.io/proposal-decorators/#sec-intro) is on github - currently in stage 2. In that proposal, they touch on possible uses for decorators. TypeScript, being a superset of JS, includes this feature. – Eric Lease Jun 23 '17 at 00:57
  • 2
    @Simon_Weaver In this case, the syntactic sugar is hiding the call to `MyEnumAware()`, where the `EnumAwareComponent` instance is passed, and has a property, `MyEnum`, added to its prototype. The value of the property is set the enum itself. This method does the same thing as the accepted answer. It's just taking advantage of the syntactic sugar proposed for decorators and allowed in TypeScript. When using Angular you're using decorator syntax right off the bat. That's what a `Component` _is_, an extension of an empty class that Angular's core classes know how to interact with. – Eric Lease Jun 23 '17 at 01:08
  • 1
    The accepted answer didn't work for me, but this one did, great ! – Random Jul 18 '17 at 07:22
  • 5
    -1: This does not appear to work with aot, resulting in `ERROR in ng:///.../whatever.component.html (13,3): Property 'MyEnum' does not exist on type 'EnumAwareComponent'`. This makes sense, because the property the decorator adds is never declared, leaving the typescript compiler unaware of its existence. – meriton Jul 26 '17 at 13:11
  • If I declare it in the component, I might as well initialize it, in which case I don't need a decorator anymore. (I know no way to declare the added property in the decorator, is there one?) – meriton Jul 27 '17 at 11:06
  • @meriton I can't answer this right now, but I will research it. Theoretically I believe it should be possible, but AOT was still in pre-release when I posted this answer, and I have been working with other technologies since then. I would recommend posting a question with your specific set of constraints if you're still pursuing this approach. – Eric Lease Jul 27 '17 at 23:30
  • 1
    @meriton The fix for that is to add `MyEnum: any` to the class definition. – koppor Aug 03 '17 at 23:26
  • 3
    So I've been using this for 4+ months. However, now that I'm doing a `--prod` build (Ionic 3 / Angular 4 / Typescript 2.4.2) it no longer works. I get the error `"TypeError: Cannot read property 'FirstValue' of undefined"`. I'm using a standard numeric enum. It works fine with AoT but not with `--prod`. It does work if I change it to using integers in the HTML, but that's not the point. Any ideas? – Russ Aug 12 '17 at 05:23
  • 1
    @Danny doesn't work for me either. `Property 'EnumNameHere' does not exist on type 'ComponentNameHere'` – georgiy.zhuravlev Jan 31 '18 at 12:14
  • Anyone encountering the 'Property does not exist on type' error with the `--aot` flag, you can use a service instead - see answer https://stackoverflow.com/a/51307059/1364650 – Vincent Sels Jul 12 '18 at 13:35
  • 1
    I found this solution, in the template you can write : {{this['MyEnum']['FirstValue']}} and you can build with --aot and --prod. If someone as a better solution? – Tumeco Aug 08 '18 at 01:32
  • does it work without the private/public internal field? I need just the enumeration, not the field – serge May 11 '22 at 08:00
57

Angular4 - Using Enum in HTML Template ngSwitch / ngSwitchCase

Solution here: https://stackoverflow.com/a/42464835/802196

credit: @snorkpete

In your component, you have

enum MyEnum{
  First,
  Second
}

Then in your component, you bring in the Enum type via a member 'MyEnum', and create another member for your enum variable 'myEnumVar' :

export class MyComponent{
  MyEnum = MyEnum;
  myEnumVar:MyEnum = MyEnum.Second
  ...
}

You can now use myEnumVar and MyEnum in your .html template. Eg, Using Enums in ngSwitch:

<div [ngSwitch]="myEnumVar">
  <div *ngSwitchCase="MyEnum.First"><app-first-component></app-first-component></div>
  <div *ngSwitchCase="MyEnum.Second"><app-second-component></app-second-component></div>
  <div *ngSwitchDefault>MyEnumVar {{myEnumVar}} is not handled.</div>
</div>
ObjectiveTC
  • 2,477
  • 30
  • 22
  • how can you reuse the same enum in a different component? – ForestG Nov 17 '17 at 12:05
  • 1
    I had to define the enum in an external file using "export enum MyEnum{...}". Then in the component file, import 'MyEnum' from that external file, and continue with the solution above for 'MyEnum = MyEnum" etc. – ObjectiveTC Nov 17 '17 at 21:49
  • 1
    Man, you are lifesaver! This increases readability immensely. For newbies like me - don't forget the MyEnum = MyEnum; line, without it switch cases don't work! – GeorgiG Jan 22 '21 at 12:20
18

as of rc.6 / final

...

export enum AdnetNetworkPropSelector {
    CONTENT,
    PACKAGE,
    RESOURCE
}

<div style="height: 100%">
          <div [ngSwitch]="propSelector">
                 <div *ngSwitchCase="adnetNetworkPropSelector.CONTENT">
                      <AdnetNetworkPackageContentProps [setAdnetContentModels]="adnetNetworkPackageContent.selectedAdnetContentModel">
                                    </AdnetNetworkPackageContentProps>
                  </div>
                 <div *ngSwitchCase="adnetNetworkPropSelector.PACKAGE">
                </div>
            </div>              
        </div>


export class AdnetNetwork {       
    private adnetNetworkPropSelector = AdnetNetworkPropSelector;
    private propSelector = AdnetNetworkPropSelector.CONTENT;
}
born2net
  • 24,129
  • 22
  • 65
  • 104
18

As an alternative to @Eric Lease's decorator, which unfortunately doesn't work using --aot (and thus --prod) builds, I resorted to using a service which exposes all my application's enums. Just need to publicly inject that into each component which requires it, under an easy name, after which you can access the enums in your views. E.g.:

Service

import { Injectable } from '@angular/core';
import { MyEnumType } from './app.enums';

@Injectable()
export class EnumsService {
  MyEnumType = MyEnumType;
  // ...
}

Don't forget to include it in your module's provider list.

Component class

export class MyComponent {
  constructor(public enums: EnumsService) {}
  @Input() public someProperty: MyEnumType;

  // ...
}

Component html

<div *ngIf="someProperty === enums.MyEnumType.SomeValue">Match!</div>
Vincent Sels
  • 2,711
  • 1
  • 24
  • 31
  • 1
    I also needed to change service and write @Injectable({providedIn: 'root'}) to make it work. Thanks! – Stalli Jun 23 '19 at 14:27
2

Start by considering 'Do I really want to do this?'

I have no problem referring to enums directly in HTML, but in some cases there are cleaner alternatives that don't lose type-safe-ness. For instance if you choose the approach shown in my other answer, you may have declared TT in your component something like this:

public TT = 
{
    // Enum defines (Horizontal | Vertical)
    FeatureBoxResponsiveLayout: FeatureBoxResponsiveLayout   
}

To show a different layout in your HTML, you'd have an *ngIf for each layout type, and you could refer directly to the enum in your component's HTML:

*ngIf="(featureBoxResponsiveService.layout | async) == TT.FeatureBoxResponsiveLayout.Horizontal"

This example uses a service to get the current layout, runs it through the async pipe and then compares it to our enum value. It's pretty verbose, convoluted and not much fun to look at. It also exposes the name of the enum, which itself may be overly verbose.

Alternative, that retains type safety from the HTML

Alternatively you can do the following, and declare a more readable function in your component's .ts file :

*ngIf="isResponsiveLayout('Horizontal')"

Much cleaner! But what if someone types in 'Horziontal' by mistake? The whole reason you wanted to use an enum in the HTML was to be typesafe right?

We can still achieve that with keyof and some typescript magic. This is the definition of the function:

isResponsiveLayout(value: keyof typeof FeatureBoxResponsiveLayout)
{
    return FeatureBoxResponsiveLayout[value] == this.featureBoxResponsiveService.layout.value;
}

Note the usage of FeatureBoxResponsiveLayout[string] which converts the string value passed in to the numeric value of the enum.

This will give an error message with an AOT compilation if you use an invalid value.

Argument of type '"H4orizontal"' is not assignable to parameter of type '"Vertical" | "Horizontal"

Currently VSCode isn't smart enough to underline H4orizontal in the HTML editor, but you'll get the warning at compile time (with --prod build or --aot switch). This also may be improved upon in a future update.

Community
  • 1
  • 1
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • not sure if I like constants inside `html` but i see your point and started using it; it does the job, as the good old days, when compiling! :) – genuinefafa Jul 18 '18 at 21:02
  • @genuinefafa this approach is really about getting the enum itself out of the html but still allowing the enum values to be compile checked. I suppose you could say it decouples html from ts but that in itself doesn’t offer any real benefits because they’re always used together. – Simon_Weaver Jul 18 '18 at 21:06
  • i like type check, specially in non- automatically tested development – genuinefafa Jul 20 '18 at 22:19
  • upvote because of opening line "Start by considering 'Do I really want to do this?'" – silvio Dec 12 '18 at 15:06
2

My component used an object myClassObject of type MyClass, which itself was using MyEnum. This lead to the same issue described above. Solved it by doing:

export enum MyEnum {
    Option1,
    Option2,
    Option3
}
export class MyClass {
    myEnum: typeof MyEnum;
    myEnumField: MyEnum;
    someOtherField: string;
}

and then using this in the template as

<div [ngSwitch]="myClassObject.myEnumField">
  <div *ngSwitchCase="myClassObject.myEnum.Option1">
    Do something for Option1
  </div>
  <div *ngSwitchCase="myClassObject.myEnum.Option2">
    Do something for Option2
  </div>
  <div *ngSwitchCase="myClassObject.myEnum.Option3">
    Do something for Opiton3
  </div>
</div>
Heribert
  • 613
  • 1
  • 9
  • 27
1

If using the 'typetable reference' approach (from @Carl G) and you're using multiple type tables you might want to consider this way :

export default class AppComponent {

  // Store a reference to the enums (must be public for --AOT to work)
  public TT = { 
       CellType: CellType, 
       CatType: CatType, 
       DogType: DogType 
  };

  ...

  dog = DogType.GoldenRetriever; 

Then access in your html file with

{{ TT.DogType[dog] }}   => "GoldenRetriever"

I favor this approach as it makes it clear you're referring to a typetable, and also avoids unnecessary pollution of your component file.

You can also put a global TT somewhere and add enums to it as needed (if you want this you may as well make a service as shown by @VincentSels answer). If you have many many typetables this may become cumbersome.

Also you always rename them in your declaration to get a shorter name.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
1

You can now do this:

for example, the enum is:

export enum MessagePriority {
    REGULAR= 1,
    WARNING,
    IMPORTANT,
}

a status message, that looks like this:

export default class StatusMessage{
    message: string;
    priority: MessagePriority;

    constructor(message: string, priority: MessagePriority){
        this.message = message;
        this.priority = priority;
    }
}

then in the .ts file of the component you can do this:

    import StatusMessage from '../../src/entities/building/ranch/administration/statusMessage';
    import { MessagePriority } from '../../enums/message-priority';
            
    export class InfoCardComponent implements OnInit {
     messagePriority: typeof MessagePriority;
                
     constructor() { 
     this.messagePriority = MessagePriority;
    }
                
    @Input() statusMessage: StatusMessage;
    ngOnInit(): void {}
}

and finally the HTML of the component looks like this:

<div class="info-card" [ngSwitch]="statusMessage.priority">
    <h2 *ngSwitchCase="this.messagePriority.REGULAR" class="info-card__regular-message">{{statusMessage.message}}</h2>
    <h2 *ngSwitchCase="this.messagePriority.WARNING" class="info-card__warning-message">{{statusMessage.message}}</h2>
    <h2 *ngSwitchCase="this.messagePriority.IMPORTANT" class="info-card__important-message">{{statusMessage.message}}</h2>
</div>

Notice that the enum is first declared to the class with the type of "typeof MessagePriority", then bound to the class by calling the definition with "this.messagePriority = MessagePriority"