Show the string representation of the enum instead of its numerical value

Can someone help me with this issue? I am trying to retrieve the role using get role(): string but it's not returning the full role name. For example, instead of getting "Head Administrator", I only get "Administrator" returned. I know that Role["Administrator"] would work, but the problem lies in IUser.role being of type Role | undefined, which complicates the situation and prevents me from simply using

get role(): string {
  return Role[this.authService.userInfo?.role];
}

so that is not an option.

header.component.ts

import { Component } from '@angular/core';

import { AuthService } from '@core/services';
import { LayoutComponent } from '../layout.component';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html'
})
export class HeaderComponent {
  get username(): string {
    return this.authService.userInfo?.username || '';
  }

  get role(): string {
    return this.authService.userInfo?.role || '';
  }

  constructor(private authService: AuthService, public app: LayoutComponent) {}

  signOut(event: any) {
    this.authService.signOut();

    event.preventDefault();
  }
}

user.model.ts

export interface IUser extends IEntity {
  email: string;
  username: string;
  role: Role;
}

export enum Role {
  Administrator = 'Head Administrator',
  DepartmentAdministrator = 'Department Administrator',
  User = 'Super User'
}

auth.service.ts

import { Injectable, OnDestroy } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';

import { BehaviorSubject, Observable, of, Subscription } from 'rxjs';
import { delay, map, tap } from 'rxjs/operators';

import { environment } from '@env';
import { IAuthResponse, IUser } from '@core/models';
import { INITIAL_AUTH_STATE } from '@core/constants';

import { JwtService } from '../util';

@Injectable({
  providedIn: 'root'
})
export class AuthService implements OnDestroy {
  private readonly TOKEN_URL = `${environment.apiUrl}/Accounts/token`;

  private currentUserSubject = new BehaviorSubject<IAuthResponse>(INITIAL_AUTH_STATE);
  private timer!: Subscription;

  currentUser$: Observable<IAuthResponse> = this.currentUserSubject.asObservable();

  get userInfo(): IUser | null {
    const accessToken = this.currentUserValue?.accessToken;

    return accessToken ? this.jwtService.decodeToken<IUser>(accessToken) : null;
  }

  private get currentUserValue(): IAuthResponse | null {
    return this.currentUserSubject.value;
  }

  private get localStorageCurrentUser(): IAuthResponse {
    const localStorageUser = localStorage.getItem('currentUser');
    return localStorageUser ? JSON.parse(localStorageUser) : INITIAL_AUTH_STATE;
  }

  constructor(
    private httpClient: HttpClient,
    private router: Router,
    private jwtService: JwtService
  ) {
    this.currentUserSubject.next(this.localStorageCurrentUser);
    window.addEventListener('storage', this.storageEventListener.bind(this));
  }

  ngOnDestroy(): void {
    window.removeEventListener('storage', this.storageEventListener.bind(this));
  }

  signIn(username: string, password: string): Observable<IAuthResponse> {
    const TOKEN_URL: string = this.TOKEN_URL + '/create';

    return this.httpClient
      .post<IAuthResponse>(TOKEN_URL, {
        username,
        password
      })
      .pipe(
        map((res) => {
          if (res && res.accessToken) {
            this.setCurrentUser(res);
          }

          return res;
        })
      );
  }

  signOut(): void {
    this.clearCurrentUser();
    this.router.navigate(['auth']);
  }

  refreshToken(): Observable<IAuthResponse | null> {
    const refreshToken = this.currentUserValue?.refreshToken;
    if (!refreshToken) {
      this.clearCurrentUser();
      return of(null);
    }

    return this.httpClient.post<IAuthResponse>(`${this.TOKEN_URL}/refresh`, { refreshToken }).pipe(
      map((res) => {
        this.setCurrentUser(res);
        return res;
      })
    );
  }

  private setCurrentUser(user: IAuthResponse): void {
    this.currentUserSubject.next(user);
    this.setLocalStorage(user);
    this.startTokenTimer();
  }

  private clearCurrentUser(): void {
    this.currentUserSubject.next(INITIAL_AUTH_STATE);
    this.clearLocalStorage();
    this.stopTokenTimer();
  }

  private setLocalStorage(userState: IAuthResponse): void {
    localStorage.setItem('currentUser', JSON.stringify(userState));
    localStorage.setItem('login-event', 'login' + Math.random());
  }

  private clearLocalStorage(): void {
    localStorage.removeItem('currentUser');
    localStorage.setItem('logout-event', 'logout' + Math.random());
  }

  private getTokenRemainingTime(): number {
    const expiresAtUtc = this.currentUserValue?.expiresAtUtc;
    if (!expiresAtUtc) {
      return 0;
    }
    const expires = new Date(expiresAtUtc);
    return expires.getTime() - Date.now();
  }

  private startTokenTimer(): void {
    const timeout = this.getTokenRemainingTime();
    this.timer = of(true)
      .pipe(
        delay(timeout),
        tap(() => this.refreshToken().subscribe())
      )
      .subscribe();
  }

  private stopTokenTimer(): void {
    this.timer?.unsubscribe();
  }

  private storageEventListener(event: StorageEvent): void {
    if (event.storageArea === localStorage) {
      if (event.key === 'logout-event') {
        this.currentUserSubject.next(INITIAL_AUTH_STATE);
      }

      if (event.key === 'login-event') {
        location.reload();
      }
    }
  }
}

Answer №1

Your code is accurate and correct.

After conducting experiments in a controlled environment, I have developed 2 functions: one to retrieve the string initializer and the other to retrieve the enum value name.

enum Role {
  Administrator = 'Head Administrator',
  DepartmentAdministrator = 'Department Administrator',
  User = 'Super User'
}

function getRoleName1(role: Role): string {
    return role
}

function getRoleName2(role: Role) {
    const roleEnum = Role as Record<string, string>
    for (const key of Object.keys(roleEnum)) {
        if (roleEnum[key] === role) {
            return key
        }
    }

    return undefined
}

console.log('1:', getRoleName1(Role.Administrator)) // Head Administrator
console.log('2:', getRoleName2(Role.Administrator)) // Administrator

The first function serves the purpose you require, and it seems similar to how you have implemented it in your code.

My suspicion is that the function may not be working as intended due to the enum not aligning with the actual data returned by the back-end, such as possibly returning 'Administrator' instead of 'Head Administrator'.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Can a constant be utilized as the property name within routerLink when specifying queryParams?

I am currently trying to update the current page by modifying or adding the query parameter "categoryId". When I attempt: <a [routerLink]="" [queryParams]="{ categoryId: category!.id }" queryParamsHandling="mer ...

Determining whether a Typescript AST node represents a javascript native function

How can I determine if an AST node in TypeScript represents a valid JavaScript function, as opposed to a custom method? Here's what I'm thinking: function isJavascriptFunction(node: ts.Node): boolean { // ----- } For instance, given the cod ...

Comparing Necessary and Deduced Generic Types in TypeScript

Can you explain the difference between these two generic types? type FnWithRequiredParam<T> = (t: T) => void type FnWithParamInferred = <T>(t: T) => void From what I understand, FnWithRequiredParam will always require the generic type t ...

The element is automatically assigned an 'any' type due to the fact that a 'string' expression cannot be utilized to index the type 'typeof'

I am facing an issue that I am having trouble understanding. The error message reads as follows: TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof The proble ...

Issue found: Passing a non-string value to the `ts.resolveTypeReferenceDirective` function

Encountering the following error: Module build failed (from ./node_modules/ts-loader/index.js): Error: Debug Failure. False expression: Non-string value passed to ts.resolveTypeReferenceDirective, likely by a wrapping package working with an outdated res ...

Creating an Angular Confirm feature using the craftpip library

Trying to utilize the angular-confirm library, but finding its documentation unclear. Implementing it as shown below: Library - In button click (login.component.ts), ButtonOnClickHandler() { angular.module('myApp', ['cp.ngConfirm']) ...

Is it possible to implement typed metaprogramming in TypeScript?

I am in the process of developing a function that takes multiple keys and values as input and should return an object with those keys and their corresponding values. The value types should match the ones provided when calling the function. Currently, the ...

The term 'shuterstock_init' is meant to be a type, however, it is mistakenly being treated as a value in this context

I am working on a service called class imageService, which mainly consists of key value pairs export type servicesName = "unsplash" | "pexels" | "pixabay" | 'shutterstock'; export type Service = { [key in services ...

What is the method for invoking a class method in Typescript from another method within the same class acting as an event handler?

I came across this TypeScript code that I need help with: class MyClass { constructor() { $("#MyButton").on("click", this.MyCallback); this.MyMethod(); } MyCallback = () => { $.ajax("http://MyAjaxUrl") ...

Removing information from the app.component.html file in Angular 6 and reflecting those updates in the view

I currently have a service that retrieves a list of data and displays it within the app.component.html. Below is the code snippet used to display the data: <ul> <li *ngFor="let data of retrieveData"> {{ data.id }} - {{data.title} ...

How is it possible that the type-checker is not flagging this code?

Do you find it acceptable that this code passes type-checking? function endlessLoop(): never { while (true) { } } let y = endlessLoop(); Why does y exist and fall under the never type category? ...

"Revamping Your Design: The Power of Angular 4

I am working with two different layouts in my project: <div *ngIf="!loginPanel" class="login1"> <a (click)="showLoginPanel()">Login</a> </div> <div *ngIf="loginPanel" class="login2"> <input type="text" placeholder="user ...

Having trouble obtaining React 15.6.1 type definitions: "ERROR: Repository not found."

Trying to set up the type definitions for React 15.6.1, but encountering an error: $ npm install --save @types/react npm ERR! git clone <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="88efe1fcc8efe1fce0fdeaa6ebe7e5">[email&# ...

How to disable click event binding in Angular2 after it has been clicked once

Within my application, there is a button that has a click event attached to it: <button class="btn btn-default" (click)="doSomething()"> I am wondering if there is a way to remove the (click) event from the button within the doSomething method so t ...

Select a random index and modify it until all unique options have been exhausted, then restart the process

My image gallery has 6 slots for images, and I have an array with a certain number of image objects: "src" : { "1x" : "/clients/Logo-1.png", "2x" : "/clients/<a href="/cdn-cg ...

Error encountered while installing Material UI in Next.js with TypeScript and pure JavaScript configurations

I'm brand new to React and Next.js, so please forgive me for asking what may seem like a silly question. I'm attempting to install Material UI in a fresh Next.js application that I created using "npx create-next-app@latest". I've been refere ...

Steps to converting an enum or literal

Hey there, I'm relatively new to working with TypeScript and I've been experimenting with transforming enums/literals using functions. For instance, creating a capitalize function that capitalizes the first letter of a string. (e.g., mapping typ ...

Refine current attributes of an object in Typescript

In typescript, I have an object of type any that needs to be reshaped to align with a specific interface. I am looking for a solution to create a new object that removes any properties not defined in the interface and adds any missing properties. An exam ...

Error in Ionic 3: "this is null"

Whenever I fetch data from Firebase, I am attempting to redirect accordingly. If the data is null or empty, then there is no need for redirection. My attempt involves using this.navCtrl.push(ProspectPage); but for some reason, it is not functioning proper ...

Angular 14.2.9: "Trouble with Form Data Binding - Seeking Assistance with Proper Data Population"

I'm currently using Angular version 14.2.9 and the component library I'm utilizing is: import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; While working on binding data to a form, I encountered an issue where the data wasn't d ...