Jak używać wartości wyliczenia w maszynie w instrukcji Angular2 ngSwitch

158

Wyliczenie Typescript wydaje się być naturalnym dopasowaniem do dyrektywy ngSwitch w Angular2. Ale kiedy próbuję użyć wyliczenia w szablonie mojego składnika, otrzymuję komunikat „Nie można odczytać właściwości 'xxx' o wartości undefined w ...”. Jak mogę używać wartości wyliczenia w szablonie składników?

Zwróć uwagę, że różni się to od sposobu tworzenia opcji wyboru html na podstawie WSZYSTKICH wartości wyliczenia (ngFor). To pytanie dotyczy ngSwitch opartego na określonej wartości wyliczenia. Chociaż pojawia się to samo podejście do tworzenia wewnętrznego odwołania klasy do wyliczenia.

Carl G
źródło
Możliwy duplikat Select na podstawie wyliczenia w Angular2
Günter Zöchbauer
1
Nie sądzę, aby te pytania były duplikatami; druga dotyczy sposobu tworzenia opcji wyboru HTML na podstawie WSZYSTKICH wartości wyliczenia (ngFor), podczas gdy ta dotyczy ngSwitch opartej na określonej wartości wyliczenia. Chociaż pojawia się to samo podejście do tworzenia wewnętrznego odwołania klasy do wyliczenia. Dziękuję za wskazanie podobieństwa.
Carl G

Odpowiedzi:

166

Możesz utworzyć odwołanie do wyliczenia w swojej klasie komponentu (właśnie zmieniłem początkowy znak na małą literę), a następnie użyć tego odniesienia z szablonu ( 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
źródło
88

Możesz utworzyć dekorator niestandardowy, który zostanie dodany do komponentu, aby był świadomy wyliczeń.

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
źródło
7
Czy ktoś odniósł sukces używając tej metody z kompilatorem AoT?
Danny,
2
Dekoratory @Simon_Weaver to zasadniczo funkcje, które przyjmują funkcję jako parametr i rozszerzają jej zachowanie. W przypadku ES6 / 7 mamy do czynienia z rozszerzeniem / adnotacją klas. Oto artykuł wysokiego poziomu o tym, jak działają . Propozycja wdrożenia w ES7 jest na github - obecnie w fazie 2. W tej propozycji, ponieważ dotykają możliwych zastosowań dla dekoratorów. TypeScript, będący nadzbiorem JS, zawiera tę funkcję.
Eric Lease
2
@Simon_Weaver W tym przypadku cukier syntaktyczny ukrywa wywołanie, do MyEnumAware()którego EnumAwareComponentjest przekazywana instancja, i ma właściwość MyEnum, dodaną do jej prototypu. Wartość właściwości to samo wyliczenie. Ta metoda działa tak samo, jak zaakceptowana odpowiedź. Po prostu wykorzystuje cukier składniowy proponowany dla dekoratorów i dozwolony w TypeScript. Używając Angulara, od razu używasz składni dekoratora. Tym właśnie Component jest a , rozszerzeniem pustej klasy, z którą podstawowe klasy Angulara wiedzą, jak współdziałać.
Eric Lease
5
-1: to nie działa z aot, w wyniku czego ERROR in ng:///.../whatever.component.html (13,3): Property 'MyEnum' does not exist on type 'EnumAwareComponent'. Ma to sens, ponieważ właściwość dodawana przez dekoratora nigdy nie jest deklarowana, co powoduje, że kompilator maszynopisu nie jest świadomy jej istnienia.
meriton
2
Więc używam tego od ponad 4 miesięcy. Jednak teraz, gdy tworzę --prodkompilację (Ionic 3 / Angular 4 / Typescript 2.4.2), już nie działa. Otrzymuję błąd "TypeError: Cannot read property 'FirstValue' of undefined". Używam standardowego wyliczenia liczbowego. Działa dobrze z AoT, ale nie z --prod. Działa, jeśli zmienię to na używanie liczb całkowitych w HTML, ale nie o to chodzi. Jakieś pomysły?
Russ
47

To proste i działa jak urok :) po prostu zadeklaruj swoje wyliczenie w ten sposób i możesz użyć go w szablonie HTML

  statusEnum: typeof StatusEnum = StatusEnum;
Aymen Boumaiza
źródło
Po dniach badań w końcu znalazłem to, czego potrzebowałem. Wielkie dzięki!
gsiradze
@Rahul StatusEnumjest zdefiniowany w jednej z .tsklas. W komponencie Angular, który importujesz, powiąż go z właściwością komponentu (tutaj statusEnum), a właściwości komponentu są dostępne z szablonu.
tom
czołgi, to jest świetne
hassan khademi
45

Angular4 - Używanie Enum w szablonie HTML ngSwitch / ngSwitchCase

Rozwiązanie tutaj: https://stackoverflow.com/a/42464835/802196

kredyt: @snorkpete

W swoim komponencie masz

enum MyEnum{
  First,
  Second
}

Następnie w swoim komponencie wprowadzasz typ wyliczenia za pośrednictwem elementu członkowskiego „MyEnum” i tworzysz kolejny element członkowski dla zmiennej wyliczeniowej „myEnumVar”:

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

Możesz teraz używać myEnumVar i MyEnum w swoim szablonie .html. Np. Używanie wyliczeń w 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
źródło
jak można ponownie użyć tego samego wyliczenia w innym składniku?
ForestG
1
Musiałem zdefiniować wyliczenie w zewnętrznym pliku za pomocą "eksportu wyliczenia MyEnum {...}". Następnie w pliku komponentu zaimportuj „MyEnum” z tego zewnętrznego pliku i kontynuuj powyższe rozwiązanie dla „MyEnum = MyEnum” itp.
ObjectiveTC
16

od 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
źródło
1
Co się zmieniło?
Carl G,
zastąpiony przez ngSwitchCase
born2net
Ah, dobrze. Dzięki!
Carl G
14

Jako alternatywę dla dekoratora @Eric Lease, który niestety nie działa przy użyciu --aot(a tym samym --prod) kompilacji, skorzystałem z usługi, która udostępnia wszystkie wyliczenia mojej aplikacji. Wystarczy publicznie wstrzyknąć to do każdego składnika, który tego wymaga, pod łatwą nazwą, po czym uzyskasz dostęp do wyliczeń w swoich widokach. Na przykład:

Usługa

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

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

Nie zapomnij umieścić go na liście dostawców modułu.

Klasa komponentu

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

  // ...
}

Komponent html

<div *ngIf="someProperty === enums.MyEnumType.SomeValue">Match!</div>
Vincent Sels
źródło
Musiałem również zmienić usługę i napisać @Injectable ({providedIn: 'root'}), aby to działało. Dzięki!
Stalli
2

Zacznij od rozważenia „Czy naprawdę chcę to zrobić?”

Nie mam problemu z odwołaniem się do wyliczeń bezpośrednio w HTML, ale w niektórych przypadkach istnieją czystsze alternatywy, które nie tracą bezpieczeństwa typów. Na przykład, jeśli wybierzesz podejście pokazane w mojej drugiej odpowiedzi, być może zadeklarowałeś TT w swoim komponencie mniej więcej tak:

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

Aby pokazać inny układ w kodzie HTML, musisz mieć *ngIfdla każdego typu układu i możesz odwołać się bezpośrednio do wyliczenia w kodzie HTML swojego komponentu:

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

W tym przykładzie użyto usługi, aby uzyskać bieżący układ, przepuszcza go przez potok asynchroniczny, a następnie porównuje go z naszą wartością wyliczenia. Jest dość rozwlekły, zawiły i niezbyt przyjemny do oglądania. Ujawnia również nazwę wyliczenia, która sama w sobie może być zbyt szczegółowa.

Alternatywa, która zachowuje bezpieczeństwo typów z kodu HTML

Alternatywnie możesz wykonać następujące czynności i zadeklarować bardziej czytelną funkcję w pliku .ts swojego komponentu:

*ngIf="isResponsiveLayout('Horizontal')"

Dużo czystsze! Ale co, jeśli ktoś 'Horziontal'przez pomyłkę wpisze ? Powodem, dla którego chciałeś użyć wyliczenia w kodzie HTML, była ochrona przed typami, prawda?

Nadal możemy to osiągnąć dzięki keyof i pewnej magii maszynopisu. Oto definicja funkcji:

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

Uwaga Wykorzystanie FeatureBoxResponsiveLayout[string]który konwertuje wartość ciągu przekazywana do wartości liczbowej wyliczenia.

Spowoduje to wyświetlenie komunikatu o błędzie z kompilacją AOT, jeśli użyjesz nieprawidłowej wartości.

Argumentu typu „H4orizontal” nie można przypisać do parametru typu „Vertical” | "Poziomy"

Obecnie VSCode nie jest wystarczająco inteligentny, aby podkreślać H4orizontalw edytorze HTML, ale ostrzeżenie zostanie wyświetlone w czasie kompilacji (za pomocą opcji --prod build lub --aot). To również może zostać poprawione w przyszłej aktualizacji.

Simon_Weaver
źródło
nie jestem pewien, czy podoba mi się stałe w środku, htmlale widzę twój punkt widzenia i zacząłem go używać; spełnia swoje zadanie, jak za starych dobrych czasów, podczas kompilacji! :)
Genuinefafa
@g Genuinefafa to podejście polega tak naprawdę na wydobyciu samego wyliczenia z html, ale nadal pozwala na sprawdzanie wartości wyliczenia. Przypuszczam, że można powiedzieć, że oddziela html od ts, ale to samo w sobie nie oferuje żadnych realnych korzyści, ponieważ są zawsze używane razem.
Simon_Weaver
Lubię sprawdzanie typu, szczególnie w przypadku rozwoju nie testowanego automatycznie
Genuinefafa
popieraj z powodu otwierającego wiersza „Zacznij od rozważenia„ Czy naprawdę chcę to zrobić? ”
WebDever
2

Mój komponent używał obiektu myClassObjecttypu MyClass, którego sam używał MyEnum. Prowadzi to do tego samego problemu, który opisano powyżej. Rozwiązałem to, wykonując:

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

a następnie używając tego w szablonie jako

<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
źródło
1

Jeśli używasz podejścia `` odwołanie do typetowania '' (z @Carl G) i używasz wielu tabel typów, możesz rozważyć ten sposób:

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; 

Następnie uzyskaj dostęp do pliku html za pomocą

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

Preferuję takie podejście, ponieważ jasno pokazuje, że odnosisz się do maszynopisu, a także pozwala uniknąć niepotrzebnego zanieczyszczenia pliku składowego.

Możesz także umieścić TTgdzieś globalną i dodać do niej wyliczenia w razie potrzeby (jeśli chcesz, możesz równie dobrze wykonać usługę, jak pokazuje odpowiedź @VincentSels). Jeśli masz wiele różnych maszynopisów, może to stać się uciążliwe.

Ponadto zawsze zmieniasz ich nazwy w deklaracji, aby uzyskać krótszą nazwę.

Simon_Weaver
źródło