Come utilizzare un valore enum dattiloscritto in un'istruzione ngSwitch Angular2


159

L'enumerazione Typescript sembra una corrispondenza naturale con la direttiva ngSwitch di Angular2. Ma quando provo ad usare un enum nel template del mio componente, ottengo "Impossibile leggere la proprietà 'xxx' di undefined in ...". Come posso usare i valori enum nel mio modello di componente?

Nota che questo è diverso da come creare opzioni di selezione html basate su TUTTI i valori di un enum (ngFor). Questa domanda riguarda ngSwitch basata su un valore particolare di un enum. Sebbene appaia lo stesso approccio alla creazione di un riferimento interno alla classe all'enum.



1
Non penso che queste domande siano duplicate; l'altro sta chiedendo come creare opzioni di selezione HTML basate su TUTTI i valori di un enum (ngFor), mentre questo riguarda ngSwitch basato su un valore particolare di un enum. Sebbene appaia lo stesso approccio alla creazione di un riferimento interno alla classe all'enum. Grazie per aver sottolineato la somiglianza.
Carl G

Risposte:


166

È possibile creare un riferimento all'enum nella classe del componente (ho appena cambiato il carattere iniziale in minuscolo) e quindi utilizzare quel riferimento dal modello ( 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;
  }
}

88

Puoi creare un decoratore personalizzato da aggiungere al tuo componente per renderlo consapevole delle enumerazioni.

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

7
Qualcuno ha avuto successo usando questo metodo con il compilatore AoT?
Danny

2
I decoratori di @Simon_Weaver sono essenzialmente funzioni che accettano una funzione come parametro ed estendono il comportamento di quella funzione. Nel caso di ES6 / 7, abbiamo a che fare con l'estensione / annotazione delle classi. Ecco un articolo di alto livello su come funzionano . La proposta per l'attuazione in ES7 è su github - attualmente nella fase 2. In quella proposta, toccano i possibili usi per i decoratori. TypeScript, essendo un superset di JS, include questa funzione.
Eric Lease,

2
@Simon_Weaver In questo caso, lo zucchero sintattico nasconde la chiamata a MyEnumAware(), dove EnumAwareComponentviene passata l' istanza e ha una proprietà MyEnum, aggiunta al suo prototipo. Il valore della proprietà è impostato sull'enum stesso. Questo metodo fa la stessa cosa della risposta accettata. Sta solo approfittando dello zucchero sintattico proposto per i decoratori e consentito in TypeScript. Quando usi Angular stai usando la sintassi del decoratore fin dall'inizio. Questo è ciò che Component è , un'estensione di una classe vuota con cui le classi principali di Angular sanno come interagire.
Eric Lease,

5
-1: Questo non sembra funzionare con aot, risultando in ERROR in ng:///.../whatever.component.html (13,3): Property 'MyEnum' does not exist on type 'EnumAwareComponent'. Questo ha senso, perché la proprietà aggiunta dal decoratore non viene mai dichiarata, lasciando il compilatore dattiloscritto ignaro della sua esistenza.
Meriton,

2
Quindi lo sto usando da 4+ mesi. Tuttavia, ora che sto realizzando una --prodbuild (Ionic 3 / Angular 4 / Typescript 2.4.2) non funziona più. Ottengo l'errore "TypeError: Cannot read property 'FirstValue' of undefined". Sto usando un enumerico numerico standard. Funziona bene con AoT ma non con --prod. Funziona se lo cambio a utilizzare numeri interi in HTML, ma non è questo il punto. Qualche idea?
Russ,

47

Questo è semplice e funziona come un fascino :) basta dichiarare la tua enum in questo modo e puoi usarla sul modello HTML

  statusEnum: typeof StatusEnum = StatusEnum;

Dopo i giorni di ricerca finalmente ho trovato quello di cui avevo bisogno. Grazie molto!
gsiradze,

@Rahul StatusEnumè definito in una delle .tsclassi. Nel componente Angolare importato, associarlo a una proprietà del componente (qui statusEnum) e le proprietà del componente sono accessibili dal modello.
tom

carri armati questo è fantastico
hassan khademi il

45

Angular4 - Utilizzo di Enum nel modello HTML ngSwitch / ngSwitchCase

Soluzione qui: https://stackoverflow.com/a/42464835/802196

credito: @snorkpete

Nel tuo componente, hai

enum MyEnum{
  First,
  Second
}

Quindi nel componente, inserisci il tipo Enum tramite un membro "MyEnum" e crei un altro membro per la tua variabile enum "myEnumVar":

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

Ora puoi usare myEnumVar e MyEnum nel tuo modello .html. Ad esempio, utilizzando Enum in 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>

come puoi riutilizzare lo stesso enum in un componente diverso?
ForestG

1
Ho dovuto definire l'enum in un file esterno usando "export enum MyEnum {...}". Quindi, nel file del componente, importare "MyEnum" da quel file esterno e continuare con la soluzione sopra per "MyEnum = MyEnum" ecc.
ObjectiveTC

16

a partire da 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
Cosa è cambiato?
Carl G,

sostituito con ngSwitchCase
born2net

Ah ok. Grazie!
Carl G,

14

In alternativa al decoratore di @Eric Lease, che purtroppo non funziona usando --aot(e quindi --prod) le build, ho fatto ricorso all'uso di un servizio che espone tutti gli enum della mia applicazione. Ho solo bisogno di iniettarlo pubblicamente in ogni componente che lo richiede, con un nome semplice, dopo di che puoi accedere alle enumerazioni nelle tue viste. Per esempio:

Servizio

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

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

Non dimenticare di includerlo nell'elenco dei provider del tuo modulo.

Classe del componente

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

  // ...
}

Componente html

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

Avevo anche bisogno di cambiare servizio e scrivere @Injectable ({givenIn: 'root'}) per farlo funzionare. Grazie!
Stalli,

2

Inizia considerando "Voglio davvero farlo?"

Non ho problemi a riferirmi alle enumerazioni direttamente in HTML, ma in alcuni casi ci sono alternative più pulite che non perdono la sicurezza del tipo. Ad esempio, se scegli l'approccio mostrato nell'altra mia risposta, potresti aver dichiarato TT nel tuo componente in questo modo:

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

Per mostrare un layout diverso nel tuo HTML, ne avresti uno *ngIfper ogni tipo di layout e potresti fare riferimento direttamente all'enum nell'HTML del tuo componente:

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

Questo esempio utilizza un servizio per ottenere il layout corrente, lo esegue attraverso la pipe asincrona e quindi lo confronta con il nostro valore enum. È piuttosto prolisso, contorto e poco divertente da guardare. Espone anche il nome dell'enum, che a sua volta può essere eccessivamente prolisso.

Alternativa, che mantiene la sicurezza dei tipi dall'HTML

In alternativa è possibile effettuare le seguenti operazioni e dichiarare una funzione più leggibile nel file .ts del componente:

*ngIf="isResponsiveLayout('Horizontal')"

Molto più pulito! E se qualcuno digitasse 'Horziontal'per errore? L'intera ragione per cui volevi usare un enum nell'HTML doveva essere dilemma giusto?

Possiamo ancora riuscirci con keyof e qualche magia dattiloscritta. Questa è la definizione della funzione:

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

Si noti l'utilizzo del FeatureBoxResponsiveLayout[string]quale converte il valore stringa passata al valore numerico della enum.

Questo darà un messaggio di errore con una compilazione AOT se si utilizza un valore non valido.

L'argomento di tipo '"H4orizontal"' non è assegnabile al parametro di tipo "" Verticale "| "Orizzontale"

Attualmente VSCode non è abbastanza intelligente da sottolineare H4orizontalnell'editor HTML, ma riceverai l'avviso in fase di compilazione (con --prod build o --aot switch). Anche questo potrebbe essere migliorato in un futuro aggiornamento.


non sono sicuro se mi piacciono le costanti dentro htmlma vedo il tuo punto e ho iniziato a usarlo; fa il lavoro, come ai bei vecchi tempi, durante la compilazione! :)
genuinefafa,

@genuinefafa questo approccio riguarda davvero l'enumerazione dell'enum stesso dall'html, ma consente comunque di verificare i valori dell'enum. Suppongo che potresti dire che disaccoppia html da ts ma che di per sé non offre alcun vantaggio reale perché sono sempre usati insieme.
Simon_Weaver,

mi piace il controllo del tipo, specialmente nello sviluppo non testato automaticamente
genuinefafa

voto a causa della linea di apertura "Inizia considerando 'Voglio davvero fare questo?'"
WebDever

2

Il mio componente usava un oggetto myClassObjectdi tipo MyClass, che a sua volta stava usando MyEnum. Ciò ha portato allo stesso problema sopra descritto. Risolto facendo:

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

e quindi usando questo nel modello come

<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

Se si utilizza l'approccio "riferimento tipizzabile" (da @Carl G) e si utilizzano più tabelle di tipi, è possibile prendere in considerazione in questo modo:

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; 

Quindi accedi al tuo file html con

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

Prediligo questo approccio in quanto chiarisce che ti stai riferendo a un testo tipizzabile ed evita anche l'inquinamento non necessario del tuo file componente.

Puoi anche mettere un globale TTda qualche parte e aggiungere enumerazioni come necessario (se lo desideri, puoi anche fare un servizio come mostrato dalla risposta di @VincentSels). Se hai molti tipi di caratteri tipografici, questo può diventare ingombrante.

Inoltre, li rinomini sempre nella tua dichiarazione per ottenere un nome più breve.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.