My front-end is Angular 12 and backend - ASP.NET Core 5. I have a CRUD page for the rooms. The problem is that roomType
is displayed with numbers - 1, 2 or 3. I want to display a description label instead. How can I do that?
public enum RoomType : int
{
SmallRoom = 1,
MediumRoom = 2,
LectureHall = 3
}
public class RoomDto : IMapFrom<Room>
{
public int Id { get; set; }
public string Name { get; set; }
public RoomType RoomType { get; set; }
public int Seats { get; set; }
public int DepartmentId { get; set; }
}
export enum RoomType {
SmallRoom = 1,
MediumRoom = 2,
LectureHall = 3
}
export interface Room {
id?: number;
name?: string;
roomType?: RoomType;
seats?: number;
departmentId?: number;
}
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { Room } from '../room';
import { RoomService } from '../room.service';
import { ConfirmationService, MessageService, PrimeNGConfig } from 'primeng/api';
import { BreadcrumbService } from '@core/services';
import { Table } from 'primeng/table';
@Component({
selector: 'app-view-rooms',
templateUrl: './view-rooms.component.html'
})
export class ViewRoomsComponent implements OnInit, OnDestroy {
roomDialog: boolean;
rooms: Room[];
room: Room;
selectedRooms: Room[];
submitted: boolean;
loading: boolean = true;
private componentDestroyed$ = new Subject<boolean>();
@ViewChild('dt') table: Table;
constructor(
private roomService: RoomService,
private messageService: MessageService,
private confirmationService: ConfirmationService,
private breadcrumbService: BreadcrumbService,
private primengConfig: PrimeNGConfig
) {
this.breadcrumbService.setItems([{ label: 'Зали' }, { label: 'Преглед' }]);
}
ngOnInit() {
this.roomService
.getAllRooms()
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((data) => {
this.rooms = data;
this.loading = false;
});
this.primengConfig.ripple = true;
}
ngOnDestroy() {
this.componentDestroyed$.next(true);
this.componentDestroyed$.complete();
}
applyFilterGlobal($event: any) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, 'contains');
}
openNew() {
this.room = {};
this.submitted = false;
this.roomDialog = true;
}
deleteSelectedRooms() {
this.confirmationService.confirm({
message: 'Сигурни ли сте, че искате да изтриете данните за избраните зали?',
header: 'Потвърждение',
icon: 'pi pi-exclamation-triangle',
acceptLabel: 'Да',
rejectLabel: 'Не',
accept: () => {
this.rooms = this.rooms.filter((val) => !this.selectedRooms.includes(val));
this.selectedRooms = [];
this.messageService.add({
severity: 'success',
summary: 'Успешно',
detail: 'Данните за залите са изтрити',
life: 3000
});
}
});
}
editRoom(room: Room) {
this.room = { ...room };
this.roomDialog = true;
}
deleteRoom(room: Room) {
this.confirmationService.confirm({
message: 'Сигурни ли сте, че искате да изтриете данните за зала ' + room.name + '?',
header: 'Потвърждение',
icon: 'pi pi-exclamation-triangle',
acceptLabel: 'Да',
rejectLabel: 'Не',
accept: () => {
this.rooms = this.rooms.filter((val) => val.id !== room.id);
this.room = {};
this.messageService.add({
severity: 'success',
summary: 'Успешно',
detail: 'Данните за залата са изтрити',
life: 3000
});
}
});
}
hideDialog() {
this.roomDialog = false;
this.submitted = false;
}
saveRoom() {
this.submitted = true;
if (this.room.name?.trim()) {
if (this.room.id) {
this.rooms[this.findIndexById(this.room.id)] = this.room;
this.messageService.add({
severity: 'success',
summary: 'Успешно',
detail: 'Данните за залата са обновени',
life: 3000
});
} else {
this.rooms.push(this.room);
// update to what's in db instead
this.messageService.add({
severity: 'success',
summary: 'Успешно',
detail: 'Залата е добавена',
life: 3000
});
}
this.rooms = [...this.rooms];
this.roomDialog = false;
this.room = {};
}
}
findIndexById(id: number): number {
let index = -1;
for (let i = 0; i < this.rooms.length; i++) {
if (this.rooms[i].id === id) {
index = i;
break;
}
}
return index;
}
}
<div class="p-grid">
<div class="p-col-12">
<p-toast></p-toast>
<div class="card">
<p-toolbar styleClass="p-mb-4">
<ng-template pTemplate="left">
<button
pButton
pRipple
label="Добавяне"
icon="pi pi-plus"
class="p-button-success p-mr-2 p-mb-2"
(click)="openNew()"
></button>
<button
pButton
pRipple
label="Изтриване"
icon="pi pi-trash"
class="p-button-danger p-mb-2"
(click)="deleteSelectedRooms()"
[disabled]="!selectedRooms || !selectedRooms.length"
></button>
</ng-template>
</p-toolbar>
<p-table
#dt
[value]="rooms"
[(selection)]="selectedRooms"
dataKey="id"
styleClass="p-datatable-rooms"
[rowHover]="true"
[rows]="10"
[showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]"
[loading]="loading"
[paginator]="true"
currentPageReportTemplate="Показват се от {first} до {last} от общо {totalRecords} записа"
[globalFilterFields]="['name', 'roomType', 'seats', 'departmentId']"
>
<ng-template pTemplate="caption">
<div class="p-d-flex p-flex-column p-flex-md-row p-jc-md-between table-header">
<h5 class="p-m-0">Зали</h5>
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input
pInputText
type="text"
(input)="applyFilterGlobal($event)"
placeholder="Търсене..."
/>
</span>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th style="width: 3rem">
<p-tableHeaderCheckbox></p-tableHeaderCheckbox>
</th>
<th pSortableColumn="name">Зала <p-sortIcon field="name"></p-sortIcon></th>
<th pSortableColumn="roomType">Тип <p-sortIcon field="roomType"></p-sortIcon></th>
<th pSortableColumn="seats">Места <p-sortIcon field="seats"></p-sortIcon></th>
<th pSortableColumn="departmentId">
Катедра <p-sortIcon field="departmentId"></p-sortIcon>
</th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-room>
<tr>
<td>
<p-tableCheckbox [value]="room"></p-tableCheckbox>
</td>
<td>
{{ room.name }}
</td>
<td>
{{ room.roomType }}
</td>
<td>
{{ room.seats }}
</td>
<td>
{{ room.departmentId }}
</td>
<td>
<button
pButton
pRipple
icon="pi pi-pencil"
class="p-button-rounded p-button-success p-mr-2"
(click)="editRoom(room)"
></button>
<button
pButton
pRipple
icon="pi pi-trash"
class="p-button-rounded p-button-warning"
(click)="deleteRoom(room)"
></button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="summary">
<div class="p-d-flex p-ai-center p-jc-between">
Общо {{ rooms ? rooms.length : 0 }} зали.
</div>
</ng-template>
</p-table>
</div>
<p-dialog
[(visible)]="roomDialog"
[style]="{ width: '450px' }"
header="Данни за зала"
[modal]="true"
styleClass="p-fluid"
>
<ng-template pTemplate="content">
<div class="p-field">
<label for="name">Зала</label>
<input
#name="ngModel"
id="name"
type="text"
pInputText
[(ngModel)]="room.name"
[ngClass]="{
'ng-dirty': (name.invalid && submitted) || (name.dirty && name.invalid)
}"
required
autofocus
/>
<small class="p-error" *ngIf="(name.invalid && submitted) || (name.dirty && name.invalid)"
>Залата е задължителна.</small
>
</div>
<div class="p-field">
<label for="roomType">Тип</label>
<input
#roomType="ngModel"
id="roomType"
type="text"
pInputText
[(ngModel)]="room.roomType"
[ngClass]="{
'ng-dirty': (roomType.invalid && submitted) || (roomType.dirty && roomType.invalid)
}"
required
/>
<small
class="p-error"
*ngIf="(roomType.invalid && submitted) || (roomType.dirty && roomType.invalid)"
>Типът е задължителен.</small
>
</div>
</ng-template>
<ng-template pTemplate="footer">
<button
pButton
pRipple
label="Отказ"
icon="pi pi-times"
class="p-button-text"
(click)="hideDialog()"
></button>
<button
pButton
pRipple
label="Запази"
icon="pi pi-check"
class="p-button-text"
(click)="saveRoom()"
></button>
</ng-template>
</p-dialog>
<p-confirmDialog [style]="{ width: '450px' }"></p-confirmDialog>
</div>
</div>
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '@env';
import { Room } from './room';
@Injectable({
providedIn: 'root'
})
export class RoomService {
private actionUrl: string;
constructor(private httpClient: HttpClient) {
this.actionUrl = `${environment.apiUrl}/rooms`;
}
getAllRooms() {
return this.httpClient.get<Room[]>(this.actionUrl);
}
getRoomById(id: number) {
return this.httpClient.get<Room>(`${this.actionUrl}/${id}`);
}
addRoom(room: Room) {
return this.httpClient.post<Room>(this.actionUrl, room);
}
updateRoom(room: Room) {
return this.httpClient.put<Room>(`${this.actionUrl}/${room.id}`, room);
}
deleteRoom(id: number) {
return this.httpClient.delete(`${this.actionUrl}/${id}`);
}
}
One option is to simply have a separate object that records metadata for every enum value:
export enum RoomType {
SmallRoom = 1,
MediumRoom = 2,
LectureHall = 3
}
export const RoomDescriptions: Record<RoomType, string> = {
[RoomType.SmallRoom]: 'A small room',
[RoomType.MediumRoom]: 'A medium room',
[RoomType.LectureHall]: 'A lecture hall',
};
const smallRoomDesc: string = RoomDescriptions[RoomType.SmallRoom];
Another is to replace your enum with an object:
export interface Room {
name: string;
description: string;
}
export const Room = (<T extends Record<keyof T, Room>>(types: T) => types)({
SmallRoom: {
name: 'Small room',
description: 'A small room',
},
MediumRoom: {
name: 'Medium room',
description: 'A medium room',
},
LectureHall: {
name: 'Lecture hall',
description: 'A lecture hall',
},
});
export type RoomTypes = keyof typeof Room;
// ^ "SmallRoom" | "MediumRoom" | "LectureHall"
const smallRoomDesc: string = Room.SmallRoom.description;
As shown as a comment, RoomTypes
is the union of all keys. The Room
constant has this as type:
That means Room.SmallRoom
is an object instead of a simple string/number. For serialization, you'd probably want to use the key instead of the object.
Alternatively, you can use this definition:
export const Room = (<T extends Record<keyof T, Room>>(types: T): { [key in keyof T]: Room } => types)({
SmallRoom: {
name: 'Small room',
description: 'A small room',
},
MediumRoom: {
name: 'Medium room',
description: 'A medium room',
},
LectureHall: {
name: 'Lecture hall',
description: 'A lecture hall',
},
});
To have the type of const Room
be nicer to look at:
Both definitions come with type checking, i.e. it would show an error if you don't define (or use the wrong type for) name
and such.