Cómo usar un valor de enumeración mecanografiado en una instrucción Angular2 ngSwitch

158

La enumeración de Typecript parece una coincidencia natural con la directiva ngSwitch de Angular2. Pero cuando trato de usar una enumeración en la plantilla de mi componente, aparece "No se puede leer la propiedad 'xxx' de undefined en ...". ¿Cómo puedo usar valores de enumeración en mi plantilla de componente?

Tenga en cuenta que esto es diferente de cómo crear opciones de selección html basadas en TODOS los valores de una enumeración (ngFor). Esta pregunta es sobre ngSwitch basado en un valor particular de una enumeración. Aunque aparece el mismo enfoque de crear una referencia interna de clase a la enumeración.

Carl G
fuente
Posible duplicado de Select basado en enum en Angular2
Günter Zöchbauer
1
No creo que estas preguntas sean duplicados; el otro pregunta cómo crear opciones de selección HTML basadas en TODOS los valores de una enumeración (ngFor), mientras que esta es sobre ngSwitch basada en un valor particular de una enumeración. Aunque aparece el mismo enfoque de crear una referencia interna de clase a la enumeración. Gracias por señalar la similitud.
Carl G

Respuestas:

166

Puede crear una referencia a la enumeración en su clase de componente (acabo de cambiar el carácter inicial a minúsculas) y luego usar esa referencia de la plantilla ( 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
fuente
88

Puede crear un decorador personalizado para agregar a su componente para que tenga conocimiento de las enumeraciones.

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
fuente
77
¿Alguien ha tenido éxito al usar este método con el compilador de AoT?
Danny
2
Los decoradores @Simon_Weaver son esencialmente funciones que toman una función como parámetro y amplían el comportamiento de esa función. En el caso de ES6 / 7, estamos tratando con la extensión / anotación de clases. Aquí hay un artículo de alto nivel sobre cómo funcionan . La propuesta de implementación en ES7 está en github, actualmente en la etapa 2. En esa propuesta, abordan posibles usos para los decoradores. TypeScript, siendo un superconjunto de JS, incluye esta característica.
Eric Lease
2
@Simon_Weaver En este caso, el azúcar sintáctico está ocultando la llamada a MyEnumAware(), donde EnumAwareComponentse pasa la instancia, y tiene una propiedad MyEnum, agregada a su prototipo. El valor de la propiedad se establece en la enumeración en sí. Este método hace lo mismo que la respuesta aceptada. Simplemente aprovecha el azúcar sintáctico propuesto para decoradores y permitido en TypeScript. Cuando usas Angular, estás usando la sintaxis del decorador desde el principio. Eso es lo que Component es , una extensión de una clase vacía con la que las clases centrales de Angular saben cómo interactuar.
Eric Lease
55
-1: Esto no parece funcionar con mucho, lo que resulta en ERROR in ng:///.../whatever.component.html (13,3): Property 'MyEnum' does not exist on type 'EnumAwareComponent'. Esto tiene sentido, porque la propiedad que agrega el decorador nunca se declara, dejando al compilador de mecanografiado ignorante de su existencia.
meriton
2
Así que he estado usando esto por más de 4 meses. Sin embargo, ahora que estoy haciendo una --prodcompilación (Ionic 3 / Angular 4 / Typecript 2.4.2) ya no funciona. Me sale el error "TypeError: Cannot read property 'FirstValue' of undefined". Estoy usando una enumeración numérica estándar. Funciona bien con AoT pero no con --prod. Funciona si lo cambio a usar enteros en el HTML, pero ese no es el punto. ¿Algunas ideas?
Russ
47

Esto es simple y funciona como un encanto :) simplemente declara tu enumeración de esta manera y puedes usarla en la plantilla HTML

  statusEnum: typeof StatusEnum = StatusEnum;
Aymen Boumaiza
fuente
Después de los días de investigación finalmente encontré lo que necesitaba. ¡Muchas gracias!
gsiradze
@Rahul StatusEnumse define en una de las .tsclases. En el componente angular que lo importa, enlácelo a una propiedad del componente (aquí statusEnum) y las propiedades del componente son accesibles desde la plantilla.
Tom
tanques esto es genial
hassan khademi
45

Angular4: uso de Enum en la plantilla HTML ngSwitch / ngSwitchCase

Solución aquí: https://stackoverflow.com/a/42464835/802196

crédito: @snorkpete

En tu componente, tienes

enum MyEnum{
  First,
  Second
}

Luego, en su componente, introduce el tipo Enum a través de un miembro 'MyEnum' y crea otro miembro para su variable enum 'myEnumVar':

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

Ahora puede usar myEnumVar y MyEnum en su plantilla .html. Por ejemplo, usando Enums en 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
fuente
¿Cómo puede reutilizar la misma enumeración en un componente diferente?
ForestG
1
Tuve que definir la enumeración en un archivo externo usando "export enum MyEnum {...}". Luego, en el archivo componente, importe 'MyEnum' desde ese archivo externo y continúe con la solución anterior 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
fuente
1
¿Que ha cambiado?
Carl G
reemplazado con ngSwitchCase
born2net
Ah bien. ¡Gracias!
Carl G
14

Como alternativa al decorador de @Eric Lease, que desafortunadamente no funciona usando --aot (y por lo tanto --prod) compilaciones, recurrí al uso de un servicio que expone todas las enumeraciones de mi aplicación. Solo necesita inyectarlo públicamente en cada componente que lo requiera, con un nombre fácil, después del cual puede acceder a las enumeraciones en sus vistas. P.ej:

Servicio

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

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

No olvide incluirlo en la lista de proveedores de su módulo.

Clase de componentes

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

  // ...
}

Componente html

<div *ngIf="someProperty === enums.MyEnumType.SomeValue">Match!</div>
Vincent Sels
fuente
También necesitaba cambiar el servicio y escribir @Injectable ({shownIn: 'root'}) para que funcione. ¡Gracias!
Stalli
2

Comience por considerar '¿ Realmente quiero hacer esto?'

No tengo ningún problema para referirme a las enumeraciones directamente en HTML, pero en algunos casos hay alternativas más limpias que no pierden la seguridad de escritura. Por ejemplo, si elige el enfoque que se muestra en mi otra respuesta, puede haber declarado TT en su componente algo como esto:

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

Para mostrar un diseño diferente en su HTML, tendría un *ngIfpara cada tipo de diseño, y podría referirse directamente a la enumeración en el HTML de su componente:

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

Este ejemplo utiliza un servicio para obtener el diseño actual, lo ejecuta a través de la tubería asíncrona y luego lo compara con nuestro valor de enumeración. Es bastante detallado, complicado y no es muy divertido de ver. También expone el nombre de la enumeración, que en sí misma puede ser demasiado verbosa.

Alternativa, que conserva la seguridad de tipo del HTML

Alternativamente, puede hacer lo siguiente y declarar una función más legible en el archivo .ts de su componente:

*ngIf="isResponsiveLayout('Horizontal')"

Mucho más limpio! Pero, ¿qué pasa si alguien escribe 'Horziontal'por error? Toda la razón por la que querías usar una enumeración en el HTML era para ser seguro.

Aún podemos lograr eso con keyof y algo de magia mecanografiada. Esta es la definición de la función:

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

Tenga en cuenta el uso de FeatureBoxResponsiveLayout[string]que convierte el valor de cadena pasado en el valor numérico de la enumeración.

Esto dará un mensaje de error con una compilación AOT si usa un valor no válido.

El argumento del tipo '"H4orizontal"' no se puede asignar al parámetro del tipo '"Vertical" | "Horizontal"

Actualmente, VSCode no es lo suficientemente inteligente como para subrayar H4orizontalen el editor HTML, pero recibirá la advertencia en el momento de la compilación (con --prod build o --aot switch). Esto también puede mejorarse en una futura actualización.

Simon_Weaver
fuente
no estoy seguro si me gustan las constantes dentro htmlpero veo tu punto y comencé a usarlo; hace el trabajo, como en los viejos tiempos, cuando compila! :)
genuinefafa 18/0718
@genuinefafa, este enfoque se trata realmente de sacar la enumeración del html, pero aún así permitir que se compilen los valores de enumeración. Supongo que se podría decir que desacopla html de ts, pero eso en sí mismo no ofrece ningún beneficio real porque siempre se usan juntos.
Simon_Weaver
me gusta la verificación de tipo, especialmente en el desarrollo no probado automáticamente
genuinefafa
voto a favor debido a la línea de apertura "Comience por considerar '¿Realmente quiero hacer esto?'"
WebDever
2

Mi componente usaba un objeto myClassObjectde tipo MyClass, que estaba usando MyEnum. Esto condujo al mismo problema descrito anteriormente. Lo resolvió haciendo:

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

y luego usar esto en la plantilla 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
fuente
1

Si utiliza el enfoque de 'referencia de tipografía' (de @Carl G) y está utilizando múltiples tablas de tipos, puede considerar de esta manera:

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; 

Luego acceda a su archivo html con

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

Estoy a favor de este enfoque, ya que deja en claro que te estás refiriendo a una tabla tipográfica y también evita la contaminación innecesaria de tu archivo componente.

También puedes poner un global TT algún lugar y agregar enumeraciones según sea necesario (si lo desea, también puede hacer un servicio como se muestra en la respuesta @VincentSels). Si tiene muchos tipos de letra, esto puede volverse engorroso.

También siempre los renombra en su declaración para obtener un nombre más corto.

Simon_Weaver
fuente