Como usar um valor de enumeração datilografado em uma instrução Angular2 ngSwitch

158

A enumeração Typescript parece uma correspondência natural com a diretiva ngSwitch do Angular2. Mas quando tento usar uma enumeração no modelo do meu componente, recebo "Não é possível ler a propriedade 'xxx' de indefinida em ...". Como posso usar valores de enumeração no meu modelo de componente?

Observe que isso é diferente de como criar opções de seleção html com base em TODOS os valores de uma enumeração (ngFor). Esta pergunta é sobre ngSwitch com base em um valor particular de um enum. Embora a mesma abordagem de criação de uma referência interna da classe ao enum seja exibida.

Carl G
fonte
Possível duplicata do Select com base na enumeração em Angular2
Günter Zöchbauer 7/16/16
1
Eu não acho que essas perguntas sejam duplicadas; a outra pergunta como criar opções de seleção HTML com base em TODOS os valores de uma enumeração (ngFor), enquanto que essa é sobre ngSwitch com base em um valor específico de uma enumeração. Embora a mesma abordagem de criação de uma referência interna da classe ao enum seja exibida. Obrigado por apontar a semelhança.
Carl G

Respostas:

166

Você pode criar uma referência ao enum na sua classe de componente (acabei de alterar o caractere inicial para minúsculo) e, em seguida, usar essa referência no modelo ( 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
fonte
88

Você pode criar um decorador personalizado para adicionar ao seu componente e torná-lo ciente das enumerações.

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-awareness.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
fonte
7
Alguém já teve sucesso usando esse método com o compilador AoT?
217 Danny
2
Os decoradores do @Simon_Weaver são essencialmente funções que assumem uma função como parâmetro e ampliam o comportamento dessa função. No caso do ES6 / 7, estamos lidando com a extensão / anotação de classes. Aqui está um artigo de alto nível sobre como eles funcionam . A proposta de implementação no ES7 está no github - atualmente no estágio 2. Nessa proposta, eles abordam possíveis usos para decoradores. O TypeScript, sendo um superconjunto de JS, inclui esse recurso.
Eric Lease
2
@Simon_Weaver Nesse caso, o açúcar sintático está ocultando a chamada para MyEnumAware(), onde a EnumAwareComponentinstância é passada e tem uma propriedade MyEnum, adicionada ao seu protótipo. O valor da propriedade é definido na própria enumeração. Este método faz a mesma coisa que a resposta aceita. É apenas tirar proveito do açúcar sintático proposto para decoradores e permitido no TypeScript. Ao usar Angular, você está usando a sintaxe do decorador logo de cara. Isso é o que Component é , uma extensão de uma classe vazia com a qual as classes principais do Angular sabem como interagir.
Eric Lease
5
-1: parece não funcionar com aot, resultando em ERROR in ng:///.../whatever.component.html (13,3): Property 'MyEnum' does not exist on type 'EnumAwareComponent'. Isso faz sentido, porque a propriedade que o decorador adiciona nunca é declarada, deixando o compilador datilografado inconsciente de sua existência.
meriton
2
Então, eu tenho usado isso por mais de 4 meses. No entanto, agora que estou fazendo uma --prodcompilação (Ionic 3 / Angular 4 / Typescript 2.4.2), não funciona mais. Eu recebo o erro "TypeError: Cannot read property 'FirstValue' of undefined". Estou usando uma enumeração numérica padrão. Funciona bem com o AoT, mas não com --prod. Funciona se eu mudar para o uso de números inteiros no HTML, mas esse não é o ponto. Alguma ideia?
Russ
47

Isso é simples e funciona como um encanto :) basta declarar seu enum assim e você pode usá-lo no modelo HTML

  statusEnum: typeof StatusEnum = StatusEnum;
Aymen Boumaiza
fonte
Após os dias de pesquisa, finalmente encontrei o que eu precisava. Muito Obrigado!
precisa saber é
@Rahul StatusEnumé definido em uma das .tsclasses. No componente Angular importado, vincule-o a uma propriedade do componente (aqui statusEnum) e as propriedades do componente podem ser acessadas no modelo.
tom
tanques isso é ótimo
HSN KH
45

Angular4 - Usando Enum no modelo HTML ngSwitch / ngSwitchCase

Solução aqui: https://stackoverflow.com/a/42464835/802196

crédito: @snorkpete

No seu componente, você tem

enum MyEnum{
  First,
  Second
}

Em seguida, em seu componente, você traz o tipo de Enum por meio de um membro 'MyEnum' e cria outro membro para sua variável de enum 'myEnumVar':

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

Agora você pode usar myEnumVar e MyEnum no seu modelo .html. Por exemplo, usando enums no 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
fonte
como você pode reutilizar a mesma enumeração em um componente diferente?
ForestG
1
Eu tive que definir a enum em um arquivo externo usando "export enum MyEnum {...}". Em seguida, no arquivo de componente, importação 'MyEnum' a partir desse arquivo externo, e continuar com a solução acima para 'MyEnum = MyEnum" etc.
ObjectiveTC
16

a partir de 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
fonte
1
O que mudou?
Carl G
substituído por ngSwitchCase
born2net 7/09/16
Ah ok. Obrigado!
Carl G
14

Como alternativa ao decorador do @Eric Lease, que infelizmente não funciona usando --aot(e, portanto --prod, compila), recorri ao uso de um serviço que expõe todas as enumerações do meu aplicativo. Só é necessário injetar publicamente isso em cada componente que exige, com um nome fácil, após o qual você pode acessar as enumerações em suas visualizações. Por exemplo:

Serviço

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

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

Não se esqueça de incluí-lo na lista de provedores do seu módulo.

Classe de componente

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

  // ...
}

Html componente

<div *ngIf="someProperty === enums.MyEnumType.SomeValue">Match!</div>
Vincent Sels
fonte
Eu também precisava alterar o serviço e escrever @Injectable ({fornecidoIn: 'root'}) para fazê-lo funcionar. Obrigado!
Stalli 23/06/19
2

Comece considerando 'Eu realmente quero fazer isso?'

Não tenho problema em me referir a enumerações diretamente em HTML, mas em alguns casos existem alternativas mais limpas que não perdem a segurança de tipo. Por exemplo, se você escolher a abordagem mostrada na minha outra resposta, poderá ter declarado TT no seu componente algo como isto:

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

Para mostrar um layout diferente no seu HTML, você deve ter um *ngIfpara cada tipo de layout e pode se referir diretamente à enumeração no HTML do seu componente:

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

Este exemplo usa um serviço para obter o layout atual, executa-o através do canal assíncrono e depois o compara ao nosso valor de enumeração. É bem detalhado, complicado e pouco divertido de se ver. Também expõe o nome da enum, que por si só pode ser excessivamente detalhado.

Alternativa, que mantém a segurança de tipo do HTML

Como alternativa, você pode fazer o seguinte e declarar uma função mais legível no arquivo .ts do seu componente:

*ngIf="isResponsiveLayout('Horizontal')"

Muito mais limpo! Mas e se alguém digitar 'Horziontal'por engano? Todo o motivo pelo qual você queria usar uma enumeração no HTML era ser tipicamente seguro, certo?

Ainda podemos conseguir isso com keyof e alguma mágica datilografada. Esta é a definição da função:

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

Observe o uso do FeatureBoxResponsiveLayout[string]qual converte o valor da seqüência de caracteres passado para o valor numérico da enumeração.

Isso fornecerá uma mensagem de erro com uma compilação AOT se você usar um valor inválido.

O argumento do tipo '"H4orizontal"' não é atribuível ao parâmetro do tipo '"Vertical" | "Horizontal"

Atualmente, o VSCode não é inteligente o suficiente para sublinhar H4orizontalno editor de HTML, mas você receberá o aviso em tempo de compilação (com --prod build ou --aot switch). Isso também pode ser aprimorado em uma atualização futura.

Simon_Weaver
fonte
não tenho certeza se gosto de constantes por dentro, htmlmas entendi o seu ponto e comecei a usá-lo; faz o trabalho, como nos bons velhos tempos, ao compilar! :)
authenticfafa
@genuinefafa Essa abordagem é realmente sobre como tirar o enum do html, mas ainda permitir que os valores do enum sejam compilados. Suponho que você poderia dizer que dissocia html de ts, mas que por si só não oferece nenhum benefício real, porque eles sempre são usados ​​juntos.
22618 Simon_Weaver
i como verificação de tipo, especialmente em não testado automaticamente desenvolvimento
genuinefafa
voto positivo por causa da linha de abertura "Comece considerando 'Eu realmente quero fazer isso?'" "
WebDever 12/12/18
2

Meu componente usou um objeto myClassObjectdo tipo MyClassque ele próprio estava usando MyEnum. Isso levou ao mesmo problema descrito acima. Resolvido fazendo:

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

e, em seguida, usando isso no modelo como

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

Se você estiver usando a abordagem 'typetable reference' (de @Carl G) e estiver usando várias tabelas de tipos, poderá considerar o seguinte:

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; 

Em seguida, acesse seu arquivo html com

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

Eu sou a favor dessa abordagem, pois deixa claro que você está se referindo a uma tabela tipográfica e também evita a poluição desnecessária do seu arquivo componente.

Você também pode colocar um global em TTalgum lugar e adicionar enumerações a ele conforme necessário (se você quiser isso, também poderá fazer um serviço, conforme mostrado pela resposta @VincentSels). Se você tiver muitas tabelas tipográficas, isso pode se tornar complicado.

Além disso, você sempre os renomeia na sua declaração para obter um nome mais curto.

Simon_Weaver
fonte