Comment utiliser une valeur d'énumération typographique dans une instruction Angular2 ngSwitch

159

L'énumération Typescript semble une correspondance naturelle avec la directive ngSwitch d'Angular2. Mais quand j'essaye d'utiliser une énumération dans le modèle de mon composant, j'obtiens "Impossible de lire la propriété 'xxx' de undefined in ...". Comment puis-je utiliser des valeurs d'énumération dans mon modèle de composant?

Veuillez noter que cela est différent de la façon de créer des options de sélection html basées sur TOUTES les valeurs d'une énumération (ngFor). Cette question concerne ngSwitch basée sur une valeur particulière d'une énumération. Bien que la même approche de création d'une référence interne à la classe à l'énumération apparaisse.

Carl G
la source
Reproduction
Günter Zöchbauer
1
Je ne pense pas que ces questions soient des doublons; l'autre demande comment créer des options de sélection HTML basées sur TOUTES les valeurs d'une énumération (ngFor), alors que celle-ci concerne ngSwitch basée sur une valeur particulière d'une énumération. Bien que la même approche de création d'une référence interne à la classe à l'énumération apparaisse. Merci d'avoir souligné la similitude.
Carl G

Réponses:

166

Vous pouvez créer une référence à l'énumération dans votre classe de composant (je viens de changer le caractère initial en minuscules), puis utiliser cette référence à partir du modèle ( 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
la source
88

Vous pouvez créer un décorateur personnalisé à ajouter à votre composant pour lui faire prendre conscience des énumérations.

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
la source
7
Quelqu'un at-il eu du succès en utilisant cette méthode avec le compilateur AoT?
Danny
2
Les décorateurs @Simon_Weaver sont essentiellement des fonctions qui prennent une fonction comme paramètre et étendent le comportement de cette fonction. Dans le cas de ES6 / 7, nous avons affaire à l'extension / annotation des classes. Voici un article de haut niveau sur leur fonctionnement . La proposition de mise en œuvre dans ES7 est sur github - actuellement à l'étape 2. Dans cette proposition, ils abordent les utilisations possibles pour les décorateurs. TypeScript, étant un sur-ensemble de JS, inclut cette fonctionnalité.
Eric Lease
2
@Simon_Weaver Dans ce cas, le sucre syntaxique cache l'appel à MyEnumAware(), où l' EnumAwareComponentinstance est passée, et a une propriété ,, MyEnumajoutée à son prototype. La valeur de la propriété est définie par l'énumération elle-même. Cette méthode fait la même chose que la réponse acceptée. Il profite simplement du sucre syntaxique proposé pour les décorateurs et autorisé dans TypeScript. Lorsque vous utilisez Angular, vous utilisez la syntaxe du décorateur dès le départ. C'est ce qu'un Component est une extension d'une classe vide que les classes de base de Angulaire savent comment interagir avec.
Eric Lease
5
-1: Cela ne semble pas fonctionner avec aot, ce qui entraîne ERROR in ng:///.../whatever.component.html (13,3): Property 'MyEnum' does not exist on type 'EnumAwareComponent'. Cela a du sens, car la propriété ajoutée par le décorateur n'est jamais déclarée, laissant le compilateur dactylographié inconscient de son existence.
meriton
2
Je l'utilise donc depuis plus de 4 mois. Cependant, maintenant que je fais une --prodcompilation (Ionic 3 / Angular 4 / Typescript 2.4.2), cela ne fonctionne plus. J'obtiens l'erreur "TypeError: Cannot read property 'FirstValue' of undefined". J'utilise une énumération numérique standard. Cela fonctionne bien avec AoT mais pas avec --prod. Cela fonctionne si je le change en utilisant des entiers dans le HTML, mais ce n'est pas le point. Des idées?
Russ
47

C'est simple et fonctionne comme un charme :) déclarez simplement votre énumération comme ceci et vous pouvez l'utiliser sur un modèle HTML

  statusEnum: typeof StatusEnum = StatusEnum;
Aymen Boumaiza
la source
Après les jours de recherche, j'ai finalement trouvé ce dont j'avais besoin. Merci beaucoup!
gsiradze
@Rahul StatusEnumest défini dans l'une des .tsclasses. Dans le composant angulaire que vous importez, liez-le à une propriété de composant (ici statusEnum) et les propriétés de composant sont accessibles à partir du modèle.
tom
chars c'est génial
hassan khademi
45

Angular4 - Utilisation d'énumération dans le modèle HTML ngSwitch / ngSwitchCase

Solution ici: https://stackoverflow.com/a/42464835/802196

crédit: @snorkpete

Dans votre composant, vous avez

enum MyEnum{
  First,
  Second
}

Ensuite, dans votre composant, vous introduisez le type Enum via un membre 'MyEnum', et créez un autre membre pour votre variable enum 'myEnumVar':

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

Vous pouvez maintenant utiliser myEnumVar et MyEnum dans votre modèle .html. Par exemple, en utilisant des énumérations dans 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>
ObjectifTC
la source
comment pouvez-vous réutiliser la même énumération dans un composant différent?
ForestG
1
J'ai dû définir l'énumération dans un fichier externe en utilisant "export enum MyEnum {...}". Ensuite, dans le fichier du composant, importez 'MyEnum' à partir de ce fichier externe, et continuez avec la solution ci-dessus pour 'MyEnum = MyEnum "etc.
ObjectiveTC
16

à 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
la source
1
Qu'est ce qui a changé?
Carl G
remplacé par ngSwitchCase
born2net
Ah ok. Merci!
Carl G
14

Comme alternative au décorateur de @Eric Lease, qui malheureusement ne fonctionne pas avec --aot(et donc --prod) les builds, j'ai eu recours à un service qui expose toutes les énumérations de mon application. Il suffit de l'injecter publiquement dans chaque composant qui le nécessite, sous un nom simple, après quoi vous pourrez accéder aux énumérations dans vos vues. Par exemple:

Un service

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

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

N'oubliez pas de l'inclure dans la liste des fournisseurs de votre module.

Classe de composant

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

  // ...
}

HTML du composant

<div *ngIf="someProperty === enums.MyEnumType.SomeValue">Match!</div>
Vincent Sels
la source
J'avais également besoin de changer de service et d'écrire @Injectable ({providedIn: 'root'}) pour le faire fonctionner. Merci!
Stalli
2

Commencez par considérer «Est-ce que je veux vraiment faire ça?

Je n'ai aucun problème à faire référence aux énumérations directement en HTML, mais dans certains cas, il existe des alternatives plus propres qui ne perdent pas la sécurité de type. Par exemple, si vous choisissez l'approche indiquée dans mon autre réponse, vous avez peut-être déclaré TT dans votre composant quelque chose comme ceci:

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

Pour afficher une mise en page différente dans votre HTML, vous auriez un *ngIfpour chaque type de mise en page, et vous pourriez vous référer directement à l'énumération dans le HTML de votre composant:

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

Cet exemple utilise un service pour obtenir la disposition actuelle, l'exécute via le canal asynchrone, puis la compare à notre valeur d'énumération. C'est assez verbeux, alambiqué et pas très amusant à regarder. Il expose également le nom de l'énumération, qui lui-même peut être trop verbeux.

Alternative, qui conserve la sécurité de type du HTML

Vous pouvez également effectuer les opérations suivantes et déclarer une fonction plus lisible dans le fichier .ts de votre composant:

*ngIf="isResponsiveLayout('Horizontal')"

Beaucoup plus propre! Mais que faire si quelqu'un tape 'Horziontal'par erreur? La seule raison pour laquelle vous vouliez utiliser une énumération dans le HTML était d'être de type sécurisé, n'est-ce pas?

Nous pouvons toujours y parvenir avec keyof et une certaine magie dactylographiée. Voici la définition de la fonction:

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

Notez l'utilisation de FeatureBoxResponsiveLayout[string]qui convertit la valeur de chaîne transmise en valeur numérique de l'énumération.

Cela donnera un message d'erreur avec une compilation AOT si vous utilisez une valeur non valide.

L'argument de type «H4orizontal» »ne peut pas être affecté au paramètre de type« Vertical »| "Horizontal"

Actuellement, VSCode n'est pas assez intelligent pour être souligné H4orizontaldans l'éditeur HTML, mais vous recevrez l'avertissement au moment de la compilation (avec le commutateur --prod build ou --aot). Cela peut également être amélioré dans une future mise à jour.

Simon_Weaver
la source
Je ne sais pas si j'aime les constantes à l'intérieur htmlmais je vois votre point et j'ai commencé à l'utiliser; ça fait le boulot, comme au bon vieux temps, lors de la compilation! :)
Genuinefafa
@g Genuinefafa cette approche consiste en fait à extraire l'énumération elle-même du html tout en permettant de vérifier la compilation des valeurs enum. Je suppose que vous pourriez dire qu'il dissocie le html de ts, mais cela en soi n'offre pas de réels avantages car ils sont toujours utilisés ensemble.
Simon_Weaver
J'aime la
upvote en raison de l'ouverture de la ligne "Commencez par considérer" Est-ce que je veux vraiment faire ça? ""
WebDever
2

Mon composant utilisait un objet myClassObjectde type MyClass, qui lui-même utilisait MyEnum. Cela conduit au même problème décrit ci-dessus. Résolu le problème en faisant:

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

puis en l'utilisant dans le modèle comme

<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
la source
1

Si vous utilisez l'approche `` référence de typetable '' (de @Carl G) et que vous utilisez plusieurs tables de types, vous voudrez peut-être envisager de cette façon:

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; 

Accédez ensuite à votre fichier html avec

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

Je suis favorable à cette approche car elle indique clairement que vous faites référence à une table de types et évite également une pollution inutile de votre fichier de composants.

Vous pouvez également placer un global TTquelque part et y ajouter des énumérations si nécessaire (si vous le souhaitez, vous pouvez également créer un service comme indiqué par la réponse @VincentSels). Si vous avez de nombreuses tables de caractères, cela peut devenir fastidieux.

De plus, vous les renommez toujours dans votre déclaration pour obtenir un nom plus court.

Simon_Weaver
la source