typescript class with a union type

Currently, I am in the process of learning ionic and angular. Within this application, I have implemented the following classes:

model.ts

export class Feed {
  constructor(
    public type: string,
    public object: ReactionObject | ObituaryObject
  ) {}
}
export class ReactionObject {
  constructor(
    public actionId: string,
    public obituaryId: string,
    public categoryId: string,
    public timestamp: string,
    public userName: string,
    public userPhoto: string,
    public deceasedName: string,
    public thanked: string,
    public community: CommunityObject,
    public obituarySponsorId: string
  ) {}
}

export class ObituaryObject {
  constructor(
    public categoryId: string,
    public deathDay: string,
    public funeral: FuneralObject,
    public name: string,
    public obituaryId: number,
    public photo: string
  ) {}
}

types.ts

export interface ApiData {
  error: string;
  session_id: string;
  data: any;
  message?: string;
}
export interface FeedData extends ApiData {
  type: string;
  object: ReactionData | SingleObituaryData;
}
export interface ReactionData extends ApiData {
  actionId: string;
  obituaryId: string;
  categoryId: string;
  timestamp: string;
  userName: string;
  userPhoto: string;
  deceasedName: string;
  thanked: string;
  community: CommunityData;
  obituarySponsorId: string;
}
export interface SingleObituaryData extends ApiData {
  categoryId: string;
  deathDay: string;
  funeral: FuneralData;
  name: string;
  obituaryId: number;
  photo: string;
}

feed.service.ts

export class FeedService {
  private _feed = new BehaviorSubject<Feed[]>([]);

  get feed() {
    return this._feed.asObservable();
  }

  constructor(private authService: AuthService, private http: HttpClient) {}

  getFeed(pageNumber: number) {
    return this.authService.userToken.pipe(
      take(1),
      switchMap((token) => {
        return this.http.get<FeedData>(
          `${environment.apiURL}getFeed&token=${token}&page=${pageNumber}`
        );
      }),
      map((resData) => {
        resData = resData.data.items;
        console.log(resData);
        const feed = [];
        for (const key in resData) {
          if (resData.hasOwnProperty(key)) {
            feed.push(
              new Feed(
                resData[key].type,
                new ReactionObject(
                  resData[key].object.actionId,
                  resData[key].object.obituaryId,
                  resData[key].object.categoryId,
                  resData[key].object.timestamp,
                  resData[key].object.userName,
                  resData[key].object.userPhoto,
                  resData[key].object.deceasedName,
                  resData[key].object.thanked,
                  resData[key].object.community,
                  resData[key].object.obituarySponsorId,
                )
              )
            );
          }
        }
        return feed;
      }),
      tap((feed) => {
        this._feed.next(feed);
      })
    );
  }
}

updates.component.html

<p class="ion-text-center" *ngIf="!isLoading && loadedFeed.length <= 0">
  No updates found
</p>

<ion-list *ngIf="isLoading" class="ion-no-padding">
  <ion-item *ngFor="let i of Arr(num).fill(1)">
    <ion-avatar slot="start">
      <ion-skeleton-text animated></ion-skeleton-text>
    </ion-avatar>
    <ion-label>
      <p>
        <ion-skeleton-text animated style="width: 80%;"></ion-skeleton-text>
      </p>
    </ion-label>
  </ion-item>
</ion-list>

<ion-list *ngIf="!isLoading && loadedFeed.length > 0" class="ion-no-padding">
  <ion-item *ngFor="let feed of loadedFeed">
    <ng-container
      *ngIf="
        feed.type === 'candle' ||
        feed.type === 'flower' ||
        feed.type === 'comment'
      "
    >
      <ion-avatar slot="start">
        <img src="../../../assets/img/{{ feed.type }}-icon.svg" />
      </ion-avatar>
      <ion-label>
        {{ feed.object.userName }} // issue here
        <ng-container *ngIf="feed.type === 'candle'">
          lit a candle on
        </ng-container>
        <ng-container *ngIf="feed.type === 'flower'">
          placed a flower on
        </ng-container>
        <ng-container *ngIf="feed.type === 'comment'">
          wrote a message on
        </ng-container>
      </ion-label>
    </ng-container>
  </ion-item>
</ion-list>

While working in VS Code, I encountered an error stating:

Identifier 'userName' is not defined. 'ReactionObject | ObituaryObject' does not contain such a member
. Despite this error, the data is displayed correctly, and the only available options in IntelliSense are categoryId and obituaryId, which are common to both classes. The error is resolved by replacing ObituaryObject with any.

Does anyone have an idea as to why this error is occurring?

Thank you!

Answer №1

It seems that the issue lies in the structure of your Feed class where the object field has two different types implemented using the concept of Union Type. Unfortunately, the ObituaryObject does not include the field userName.

To address this, I would recommend refraining from using the UnionType and instead opting for a parent object approach leveraging TypeScript inheritance.

This adjustment will not only enhance the clarity of your code but also streamline the structure.

Access the complete solution HERE.

Model / View / Component

Your updated model will look like this:

export class GenObject {
  constructor(
    public obituaryId: number,
    public categoryId: string
  ) {
  }
}

export class Feed {
  constructor(
    public type: string,
    public object: GenObject
  ) {
  }
}

export class ReactionObject extends GenObject {
  constructor(
    public actionId: string,
    public userName: string,
    public obituaryId: number,
    public categoryId: string
  ) {
    super(obituaryId, categoryId);
  }
}

export class ObituaryObject extends GenObject {
  constructor(
    public deathDay: string,
    public name: string,
    public obituaryId: number,
    public categoryId: string
  ) {
    super(obituaryId, categoryId);
  }
}

When accessing the field in the view, ensure to explicitly check the specific class like so:

 {{ getReaction(obituaryObject.object).username }}

You can define the method as follows:

getReaction(object: GenObject) {
   if (object instanceof ReactionObject) {
     return object as ReactionObject;
   }
 }

Answer №2

declaring object as either of ReactionObject or ObituaryObject

this specifies that the object can be of type ReactionObject or ObituaryObject

 {{ feed.object.userName }} // issue identified here

Modify to:

  {{ (feed.object as ReactionObject).userName }} // issue should be fixed.

or

  {{ (<ReactionObject>feed.object).userName }} // issue should be resolved.

Don't forget to import ReactionObject in `updates.component.ts

import { ReactionObject } from './model.ts'  // double check the path for model.ts

Update:

In updates.components.ts, introduced a getter function

get feedObjectUserName() { 
  return ( <RectionObject> feed.object).userName
}

and in updates.component.html, made the following change

{{ feedObjectUserName }}

Wishing you success with this solution!

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

The MatInput value will only display after the page is reloaded or refreshed

After refreshing the page, a matInput field displays a value or result that was previously hidden. https://i.stack.imgur.com/q9LQI.png By selecting or highlighting within the matInput, the value or result becomes visible. https://i.stack.imgur.com/SqaLA.p ...

The function Array.foreach is not available for type any[]

I'm encountering an issue where when I attempt to use the ".forEach" method for an array, an error message stating that the property 'forEach' does not exist on type 'any[]' is displayed. What steps can I take to resolve this probl ...

Looking to split the month and year input boxes when utilizing stripe version 7.2.0

Currently, I am utilizing ngx-stripe Version 7.2.0 to manage UI elements in Angular. I'm wondering if there is a method to split the Month and Year into separate text boxes within the UI of Angular 7 instead of having them combined into one field? ...

Issues arise when attempting to construct an angular docker image

After attempting to create an angular docker image, I encountered the following error message following the execution of npm run build --prod: executor failed running [/bin/sh -c npm run build --prod]: exit code: 1 I am seeking clarification on this iss ...

Retail Shop versus Requesting Services While Out and About

Currently, I am in the process of implementing an API for Q&A on my portal and I am faced with two options: To hard code the categories into the HTML, and only load the questions from the API when the category component is opened (which will be displayed ...

Angular2 and ReactiveX: Innovative Pagination Strategies

Currently, I am diving into the world of ReactiveX. To make things easier to understand, I have removed error checking, logging, and other unnecessary bits. One of my services returns a collection of objects in JSON format: getPanels() { return this. ...

In Typescript, an index signature parameter can only be of type 'string' or 'number'

I'm facing an issue with a generic type that defaults to string: interface EntityState<typeOfID = string> { entities: { [ id: typeOfID]: any }; } The error I receive is: An index signature parameter type must be either 'string' or ...

Encountering issues while attempting to upload items to AWS S3 bucket through NodeJS, receiving an Access Denied error 403

I encountered an issue while attempting to upload objects into AWS S3 using a NodeJS program. 2020-07-24T15:04:45.744Z 91aaad14-c00a-12c4-89f6-4c59fee047a1 INFO uploading to S3 2020-07-24T15:04:47.383Z 91aaad14-c00a-12c4-89f6-4c59fee047a1 IN ...

Can a type name be converted into a string representation for use as a template literal type?

Is there a way to retrieve the string representation of a type name in order to return a more concise compile error message from a type function? I came across this solution (unfortunately, the article does not have anchor links so you will need to search ...

No declaration file was located for the module '@handsontable/react' despite the presence of a 'd.ts' file

Embarking on a fresh project using vite+react+ts+swc by executing the command below as per the vite documentation. npm create vite@latest -- --template react-swc-ts Additionally, I integrated the handsontable library into my setup with the following comm ...

A Unique Identifier in Kotlin

In my typescript class, I have a member that accepts any as the name: interface ControlTagType { type?: String | null; [name: string]: any } class ControlTag { tagSource: String | null = null; tag: ControlTagType | null = null; } expor ...

Filter the output from a function that has the ability to produce a Promise returning a boolean value or a

I can't help but wonder if anyone has encountered this issue before. Prior to this, my EventHandler structure looked like: export interface EventHandler { name: string; canHandleEvent(event: EventEntity): boolean; handleEvent(event: EventEntity ...

Is there a way to integrate TypeScript into the CDN version of Vue?

For specific areas of my project, I am utilizing the Vue CDN. I would like to incorporate Typescript support for these sections as well. However, our technical stack limitations prevent us from using Vue CLI. Is there a method to import Vue.js into a bas ...

Enhance your Angular application with lazy loading and nested children components using named outlets

Let me start by explaining that the example provided below is a simplified version of my routes that are not functioning properly. I am working on an angular project, specifically a nativescript angular project, and I suspect the issue lies within Angular ...

How can you create a unique record by appending a number in Javascript?

Currently, when a file already exists, I add a timestamp prefix to the filename to ensure it is unique. However, instead of using timestamps, I would like to use an ordinal suffix or simply append a number to the filename. I am considering adding an incr ...

Optimizing my AngularJS bundle is pushing me towards upgrading to Angular 5

Regarding my AngularJS application, it was initially created using 'yo angular-fullstack' with JS scripting instead of TS. It is functional but experiencing performance and user experience issues. The deployment is on AWS ElasticBeanstalk nano i ...

observe the file currently residing on the server

Is there a way to display a server-based file within an HTML page using Angular 8.0.0? I attempted to use ngx-file-viewer, but encountered the error "Property 'wheelDelta' does not exist on type 'WheelEvent'". To address this issue, I ...

Tips for evaluating a scrollTop occurrence using Jest in Angular 8

I've been struggling to find a solution to test the scrollTop event for the past couple of days, but unfortunately, I have not been successful in resolving it. Every attempt I've made has led to the same error message... TypeError: Cannot read p ...

The TypeScript error message states that a value of 'undefined' cannot be assigned to a type that expects either a boolean, Connection

I've been grappling with this code snippet for a while now. It was originally written in JavaScript a few months back, but recently I decided to delve into TypeScript. However, I'm struggling to understand how data types are properly defined in T ...

Developing React component libraries with TypeScript compared to Babel compiler

Currently, I'm utilizing the babel compiler for compiling my React component libraries. The initial choice was influenced by Create React App's use of the same compiler. However, I've encountered challenges with using babel for creating libr ...