134

I'm using Angular2 and TypeScript and 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?

Axifive
  • 1,159
  • 2
  • 19
  • 31
Rob Gorman
  • 3,502
  • 5
  • 28
  • 45
  • 1
    Please, consider updating the accepted answer to Murolack's one as it is the easiest and most up-to-date one. Thanks! – Yulian Oct 12 '19 at 10:19

13 Answers13

151

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

pjpscriv
  • 866
  • 11
  • 20
Murolack
  • 1,897
  • 1
  • 14
  • 17
  • 10
    It worked but sorted the order of the enum alphabetically. – Ravi R Priyadarshi Jan 13 '20 at 13:37
  • 3
    @RRaj you can control sort order: https://stackoverflow.com/a/52794221/5253897 – Christian Jensen Sep 14 '20 at 22:45
  • 11
    This doesn't work for me, as angular iterates through every key and every value for me... The functionality must've been modified in later versions... :( – fonzane Nov 23 '21 at 14:39
  • 7
    Keep in mind that you can't use the enum directly like this with TestEnum, you have to set the enum to a variable like: public testEnum = TestEnum; Then in your loop you can use let enum of testEnum | keyvalue – John Doe Nov 26 '21 at 09:42
  • 2
    @fonzane, it seems that as enums are default numeric, then ts maps k/v pairs for names and values; and values and names (for indexing, I guess)... To solve this, I mapped my (string) enums to a value: `enum Food { Chips = 'chips', Fish = 'fish' }`, which solved the problem. Far beter details here, https://www.angularjswiki.com/angular/names-of-enums-typescript/ – Ctrl-Zed Apr 12 '22 at 12:37
  • It works. But You should pay attention your enum's values. If the enum's values are number, The result will not be as You expected(2 times iteration happens) – Odilbek Utamuratov Sep 07 '22 at 06:40
  • a workaround to avoid double iteration: `

    {{ enum.key }} - {{ enum.value}}

    `
    – Xel Jun 06 '23 at 23:51
114

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.

yurzui
  • 205,937
  • 32
  • 433
  • 399
17

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

Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Thanks for the help. I just tried this but get the error "NgFor only supports binding to Iterables such as Arrays." So it looks like I could create a pipe to tranform roles to an array of enums or strings. But it seems to me like I ought to be able to do this natively somehow. – Rob Gorman Jul 24 '16 at 17:14
  • Sorry, forgot the pipe. Updated my answer. – Günter Zöchbauer Jul 24 '16 at 17:18
  • 1
    Thanks! Better solution than the cast to Array :-) – Axel Schmitz Oct 25 '17 at 06:10
  • I get the error Argument of type '{ imports: (typeof EnumToArrayPipe)[]; selector: string; templateUrl: string; styleUrls: string[]; }' is not assignable to parameter of type 'Component'. Object literal may only specify known properties, and 'imports' does not exist in type 'Component'. When I put it in imports in the ngModule, it can't find the pipe. Any idea what's wrong? – GeekPeek Sep 19 '19 at 14:29
  • That's an old answer. Several things have changed since. Create a module and add the pipe to `declarations: [EnumToArrayPipe], ` and add the module to `imports: [...]` of every module where you want to use the pipe. The answer I linked to above shows how. – Günter Zöchbauer Sep 19 '19 at 16:35
  • 1
    Using / referencing the pipe must be done with enum(s)ToArray as can be seen in the declaration of the pipe (name). I made the edit. – Youp Bernoulli Jan 24 '20 at 19:40
9

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.)

  • 1
    and to get values, you'd do the opposite: let values = Object.keys(ApplicationMode); return values.slice(0, values.length / 2); Thank you for the idea. – Andrei Drynov Nov 09 '16 at 12:08
  • there's no "keys" entry before the slice hence you don't need `keys.lenght-1` – KlsLondon Sep 27 '17 at 10:35
8

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.

Rob Gorman
  • 3,502
  • 5
  • 28
  • 45
6
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;
  }
}
Neo_Ryu
  • 111
  • 2
  • 4
5

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>
EladTal
  • 2,167
  • 1
  • 18
  • 10
4

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]}));
Sameera R.
  • 4,384
  • 2
  • 36
  • 53
3

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>
Pax Beach
  • 2,059
  • 1
  • 20
  • 27
  • the usage of the methods inside the ngFor will cause a big bunch of change detections as function return value changes are not detectable very well. You should put the "fileVals()" return value into a variable and read it from there. – seawave_23 Apr 04 '19 at 08:45
2

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.

Cedric Arnould
  • 1,991
  • 4
  • 29
  • 49
  • where notificationGrouping is defined? – LearningPal Feb 19 '19 at 14:18
  • 1
    although initially I expected a more simple solution already provided out of the box by typescript or angular (that wouldn't include any sorting by default), your solution is perfect, and I learned something new today: Pipes! :) Thank you very much – Mihai Cicu Mar 24 '20 at 08:36
1

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.

Dovev Hefetz
  • 1,346
  • 14
  • 21
0
 fillKeysValueFromEnum<T>(type:T){
    return Object.keys(type).filter(t => isNaN(+t)).map(el => {
       return {
         key: el,
         value: Object(type)[el]
       }
     });
  }

Then

fillKeysValueFromEnum(ENUM_HERE)
Hoopou
  • 13
  • 1
  • 6
0

I am very late to it and my answer might not directly solve the question but it gets the job done. Today I came across a ask which revolved around the same problem, i.e., iterating enum.

I took to creating a object instead of using enum.

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

To iterate use like below:

let Number=Number; 
let roles= Roles; // in .ts file
<select>
    <option *ngFor="let role of roles | keyvalue" [value]="Number({{role.key}})">
        {{role.value}}
    </option>
</select>

To parse a value use like below:

let selectedRoleValue= 4; 
let Roles= Roles; // in .ts file

<div>{{Roles[selectedRoleValue]}}</div>

This will display

CrewMember

Abhishek Tewari
  • 387
  • 3
  • 12