89
votes

I am using Angular2 and Typscript. I have an enum:

export enum Role {
    ServiceAdmin, CompanyAdmin, Foreman, AgentForeman, 
    CrewMember, AgentCrewMember, Customer
}

I want to use *ngFor to iterate over the enum. What is the best way to do this? Must I create a Pipe? Or is there a simpler way?

11
Please, consider updating the accepted answer to Murolack's one as it is the easiest and most up-to-date one. Thanks!Yulian

11 Answers

96
votes

An enum is just an object.

Your enum is written something like this in JavaScript:

{
    0: "ServiceAdmin", 
    1: "CompanyAdmin", 
    2: "Foreman", 
    3: "AgentForeman", 
    4: "CrewMember", 
    5: "AgentCrewMember", 
    6: "Customer", 
    ServiceAdmin: 0, 
    CompanyAdmin: 1, 
    Foreman: 2, 
    AgentForeman: 3, 
    CrewMember: 4,
    AgentCrewMember: 5,
    Customer: 6
}

So you can iterate it this way (plnkr):

@Component({
    ...
    template: `
    <div *ngFor="let item of keys()">
      {{ item }}
    </div>  
  `
})
export class YourComponent {
    role = Role;
    keys() : Array<string> {
        var keys = Object.keys(this.role);
        return keys.slice(keys.length / 2);
    }
}

Or would be better to create custom pipe:

@Pipe({
  name: 'enumToArray'
})
export class EnumToArrayPipe implements PipeTransform {
  transform(data: Object) {
    const keys = Object.keys(data);
    return keys.slice(keys.length / 2);
  }
}

Example

Update

Typescript 2.4 allows enum members to contain string initializers like:

enum Colors {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE",
}

in this case you can just return Object.keys(data); from pipe.

80
votes

You can just use the "keyvalue" pipe introduced in Angular 6.1.

<p *ngFor="let enum of TestEnum | keyvalue">
  {{ enum.key }} - {{ enum.value}}
</p>

See here for a full example -> https://stackblitz.com/edit/angular-gujg2e

15
votes

The scope of the template is the component instance. If you want to access something outside this scope you need to make it available from withing your component instance:

This also works if the enum keys do not start with 0

@Pipe({name: 'enumToArray'})
export class EnumToArrayPipe implements PipeTransform {
  transform(value) : Object {
    return Object.keys(value).filter(e => !isNaN(+e)).map(o => { return {index: +o, name: value[o]}});
  }
}

@Component({
  ...
  imports: [EnumsToArrayPipe],
  template: `<div *ngFor="let item of roles | enumToArray">{{item.index}}: {{item.name}}</div>`
})
class MyComponent {
  roles = Role;
}

See also https://stackoverflow.com/a/35750252/217408

9
votes

I needed to do the same thing and maybe this is what you wanted.
More DRY and it can be used with module too.

export enum Role {
    ServiceAdmin, CompanyAdmin, Foreman, AgentForeman, 
    CrewMember, AgentCrewMember, Customer
}

export namespace Role {

  export function keys(): Array<string>{
    var keys = Object.keys(Role);
    return keys.slice(keys.length / 2, keys.length-1);
  }
}

the object output before the slice

{
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "ServiceAdmin",
    "CompanyAdmin",
    "Foreman",
    "AgentForeman",
    "CrewMember",
    "AgentCrewMember",
    "Customer",
    "keys"
}

typescript merges the two declarations hence the keys.lenght-1

and the ngFor:

<div *ngFor="let role of Roles.keys()">{{ role }}</div>

more info:
Typescript's Declaration merging

based on:
TypeScript: Add functions to an Enum https://basarat.gitbooks.io/typescript/content/docs/enums.html (at the end of the enums chapter.)

7
votes

After further research and review of the other answers I now can formulate an answer to my question. I think its not possible to just use *ngFor to iterate over an enum without some code support in the component. The code support can consist of constructor code that turns the Enum into some sort of array or we can create a custom pipe that does something similar.

5
votes
export enum Priority {
  LL = 1,   // VERY LOW
  L = 2,    // LOW
  N = 3,    // NORMAL
  U = 4,    // HIGH
  UU = 5    // VERY HIGH
}

Your angular component.ts :

import { Priority } from './../shared/core/config/datas.config';

@Component({
  selector: 'app-yourcomponent',
  template: `
    <ng-container *ngFor="let p of getPriority">
       <div> {{p.key}} / {{p.value}} </div>
    </ng-container> 
  `
})

export class YourComponent {
  getPriority = this.getENUM(Priority);

  getENUM(ENUM:any): string[] {
    let myEnum = [];
    let objectEnum = Object.keys(ENUM);
    const values = objectEnum.slice( 0 , objectEnum.length / 2 );
    const keys = objectEnum.slice( objectEnum.length / 2 );

    for (let i = 0 ; i < objectEnum.length/2 ; i++ ) {
      myEnum.push( { key: keys[i], value: values[i] } ); 
    }
    return myEnum;
  }
}
3
votes

I have the enum:

export enum FileCategory {
  passport = 'Multipass',
  agreement = 'Personal agreement',
  contract = 'Contract',
  photo = 'Self photos',
  other = 'Other'
}

In the component ts file:

export class MyBestComponent implements OnInit {
  fileCategory = FileCategory;

  // returns keys of enum
  fileKeys(): Array<string> {
    const keys = Object.keys(this.fileCategory);
    return keys;
  }

  // returns values of enum
  fileVals(): Array<string> {
    const keys = Object.keys(this.fileCategory);
    return keys.map(el => Object(this.fileCategory)[el]);
  }

In the HTML template display these enum's values and keys:

  <a *ngFor="let cat of fileVals()"
     (click)="addFileCategory(cat)">{{cat}}</a>
  <a *ngFor="let cat of fileKeys()"
     (click)="addFileCategory(cat)">{{cat}}</a>
3
votes

using pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'enum'
})
export class EnumSelectPipe implements PipeTransform {
  transform(value: any): [number, string][] {
    return Object.keys(value).filter(t => isNaN(+t)).map(t => [value[t], t]);
  }
}

and in the template:

<mat-select formControlName="type" placeholder="Package Type">
  <mat-option *ngFor="let pType of PackageTypes | enum" [value]="pType[0]">{{ pType[1] | title}}</mat-option>
</mat-select>
2
votes

I recommend you to use a generic Pipe, it will be more flexible and less redundant in your code. The problem with some previous propositions is that the typescript allow you to have different kind of enum, not only number/string.

For example:

export enum NotificationGrouping {
    GroupByCreatedAt = "GroupByCreatedAt", 
    GroupByCreatedByUser = "GroupByCreatedByUser", 
    GroupByEntity = "GroupByEntity", 
    GroupByAction = "GroupByAction", 
}

Here is my solution:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'enumToArray'
})
export class EnumToArrayPipe implements PipeTransform {

  transform(value, args: string[]): any {
    let result = [];
    var keys = Object.keys(value);
    var values = Object.values(value);
    for (var i = 0; i < keys.length; i++) {
      result.push({ key: keys[i], value: values[i] });
    }
    return result; 
    //or if you want to order the result: 
    //return result.sort((a, b) => a.value < b.value ? -1 : 1);
  }
}

and the html will be:

<mat-select [(ngModel)]="groupKey">
  <mat-option *ngFor="let group of notificationGrouping | enumToArray"
              [value]="group.key">
    {{ group.value }}
  </mat-option>
</mat-select>

in ts:

public notificationGrouping : NotificationGrouping

Note: Still interesting to see people putting a minus without explanation ... For others who could be interested by this solution, I can confirm that it works correctly.

2
votes

ES6 supports

export enum E {
    a = 'First',
    b = 'Second',
    c = 'Third'
}

let keyValueArray = Object.keys(E).map(k => ({key: k, value: E[k as any]}));
1
votes

In Angular 7, still getting a list of all keys and values when using keys().

Based on the above answers I am using this for a simple ENUM, seems cleaner and more OO:

export enum CategoryType {
    Type1,
    Type2,
    ...,
}

export namespace CategoryType {
    export function keys() {
        return Object.keys(CategoryType).filter(k => !isNaN(Number(k)));
    }
}

then in the template:

<option *ngFor="let type of types.keys()" [value]="type">{{types[type]}}</option>

The function becomes another entry in the enum, but gets filtered out like the other non-numbers.