如何在Angular2 ngSwitch语句中使用打字稿枚举值


157

Typescript枚举似乎与Angular2的ngSwitch指令自然匹配。但是,当我尝试在组件模板中使用枚举时,出现“无法读取...中未定义的属性'xxx'”。如何在组件模板中使用枚举值?

请注意,这与基于枚举(ngFor)的所有值创建html select选项的方式不同。这个问题是关于基于特定枚举值的ngSwitch的。尽管出现了创建对枚举的类内部引用的相同方法。



1
我不认为这些问题是重复的。另一个是询问如何基于一个枚举(ngFor)的所有值创建HTML选择选项,而这个是关于ngSwitch的一个特定枚举值。尽管出现了创建对枚举的类内部引用的相同方法。感谢您指出相似之处。
卡尔·G

Answers:


166

您可以在组件类中创建对枚举的引用(我只是将初始字符更改为小写),然后从模板(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;
  }
}

87

您可以创建一个自定义装饰器以添加到您的组件中,以使其了解枚举。

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;
}

枚举感知组件

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;
  }
}

7
有没有人在AoT编译器上成功使用此方法?
丹尼

2
@Simon_Weaver 装饰器本质上是将函数作为参数并扩展该函数行为的函数。对于ES6 / 7,我们正在处理类的扩展/注释。这是一篇有关它们如何工作的高级文章在ES7中实现提案位于github上-目前处于第2阶段。在该提案中,他们涉及装饰器的可能用途。TypeScript是JS的超集,其中包括此功能。
Eric Lease

2
@Simon_Weaver在这种情况下,语法糖隐藏了对的调用MyEnumAware()EnumAwareComponent实例在此处传递,并MyEnum在其原型中添加了属性。该属性的值是设置枚举本身。此方法与接受的答案具有相同的作用。它只是利用为装饰器建议的语法糖,并在TypeScript中允许使用。使用Angular时,您马上就使用了装饰器语法。这是一个什么Component ,空类的扩展角的核心类知道如何与互动。
Eric Lease

5
-1:这似乎不适用于aot,结果是ERROR in ng:///.../whatever.component.html (13,3): Property 'MyEnum' does not exist on type 'EnumAwareComponent'。这是有道理的,因为从未声明装饰器添加的属性,而使打字稿编译器不知道其存在。
meriton '17

2
所以我已经使用了4个月以上。但是,现在我正在--prod构建(Ionic 3 / Angular 4 / Typescript 2.4.2),它不再起作用。我得到了错误"TypeError: Cannot read property 'FirstValue' of undefined"。我正在使用标准的数字枚举。它与AoT兼容,但不适用于--prod。如果我将其更改为在HTML中使用整数,它确实可以工作,但这不是重点。有任何想法吗?
Russ

47

这很简单,就像一个魅力:)只需这样声明您的枚举即可,您可以在HTML模板上使用它

  statusEnum: typeof StatusEnum = StatusEnum;

经过几天的研究终于找到了我所需要的。非常感谢!
gsiradze

@Rahul StatusEnum是在其中一个.ts类中定义的。在您导入Angular组件中,将其绑定到组件属性(在此处statusEnum),并且可以从模板访问组件属性。
汤姆·

坦克太棒了
HSN KH

45

Angular4-在HTML模板ngSwitch / ngSwitchCase中使用枚举

解决方案在这里:https : //stackoverflow.com/a/42464835/802196

信用:@snorkpete

在组件中,您有

enum MyEnum{
  First,
  Second
}

然后在组件中,通过成员'MyEnum'引入Enum类型,并为枚举变量'myEnumVar'创建另一个成员:

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

现在,您可以在.html模板中使用myEnumVar和MyEnum。例如,在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>

如何在不同的组件中重用相同的枚举?
ForestG

1
我必须使用“导出枚举MyEnum {...}”在外部文件中定义枚举。然后在组件文件中,从该外部文件中导入“ MyEnum”,并继续执行上面的“ MyEnum = MyEnum”等解决方案。–
ObjectiveTC

16

截至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;
}

1
有什么变化?
卡尔·G

与ngSwitchCase取代
born2net

啊好吧。谢谢!
卡尔·G

14

作为@Eric Lease的装饰器的替代方案,不幸的是,该装饰器无法使用构建--aot(因此--prod)进行构建,因此我求助于使用公开我所有应用程序枚举的服务。只需以简单的名称将其公开地注入到需要它的每个组件中,之后您就可以访问视图中的枚举了。例如:

服务

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

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

不要忘记将其包括在模块的提供者列表中。

组件类

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

  // ...
}

组件html

<div *ngIf="someProperty === enums.MyEnumType.SomeValue">Match!</div>

我还需要更改服务并编写@Injectable({providedIn:'root'})使其起作用。谢谢!
Stalli

2

首先考虑“我真的要这样做吗?”

我在HTML中直接引用枚举没有问题,但是在某些情况下,还有一些更干净的替代方案不会丢失类型安全性。例如,如果您选择我的其他答案中所示的方法,则可能已在组件中声明了TT,如下所示:

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

要在HTML中显示不同的布局,您需要*ngIf为每种布局类型提供一个,并且可以直接引用组件HTML中的枚举:

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

本示例使用服务获取当前布局,通过异步管道运行它,然后将其与我们的枚举值进行比较。它很冗长,令人费解,看起来也没有太多乐趣。它还暴露了枚举的名称,该名称本身可能过于冗长。

替代方法,保留了HTML的类型安全性

另外,您可以执行以下操作,并在组件的.ts文件中声明一个更具可读性的函数:

*ngIf="isResponsiveLayout('Horizontal')"

干净得多!但是,如果有人'Horziontal'错误输入怎么办?您想要在HTML中使用枚举的全部原因是类型安全的,对吧?

我们仍然可以使用keyof和一些打字稿魔术来实现。这是函数的定义:

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

需要注意的使用FeatureBoxResponsiveLayout[string],其转换传递给枚举的数值的字符串值。

如果您使用无效的值,这将在AOT编译中给出错误消息。

类型'“ H4orizo​​ntal”'的参数不能分配给类型'“ Vertical” |的参数。“水平”

当前,VSCode不够智能,无法H4orizontal在HTML编辑器中加下划线,但是您会在编译时收到警告(使用--prod build或--aot开关)。在将来的更新中可能还会对此进行改进。


不知道我是否喜欢里面的常量,html但是我明白了你的观点并开始使用它;就像过去的美好时光一样,它可以完成工作!:)
authenticfafa

@genuinefafa这种方法实际上是将枚举本身从html中删除,但仍然允许对枚举值进行编译检查。我想您可以说它使ts与html脱钩,但是它本身并没有提供任何真正的好处,因为它们总是一起使用。
Simon_Weaver

我喜欢类型检查,尤其是在未经自动测试的开发中
authenticfafa

由于打开了行而开始
投票

2

我的组件使用了本身正在使用myClassObject的类型的对象。这导致上述相同的问题。通过执行以下操作解决了该问题:MyClassMyEnum

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

然后在模板中使用

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

1

如果使用“类型表引用”方法(来自@Carl G),并且您正在使用多个类型表,则可能需要考虑这种方式:

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; 

然后使用

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

我喜欢这种方法,因为它可以清楚地表明您在引用一个类型表,还可以避免不必要地污染组件文件。

您还可以将全局TT变量放置在某个位置,并根据需要向其添加枚举(如果需要,您也可以提供@VincentSels答案所示的服务)。如果您有许多类型表,这可能会变得很麻烦。

同样,您总是在声明中重命名它们以获得较短的名称。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.