Setting up a connection between two distinct interfaces without combining them in Typescript

As a newcomer to typescript, imagine having the following scenario:

class Foo{
  options: fooOptionsObj;
  constructor(options: fooOptionsObj){
    this.options = options;
  }
  sayMessage(){
      console.log(`I am number${this.options.position}, and I say ${this.options.message}`);
  }
}

interface fooOptionsObj{
  message: string;
  position: number;
}

You can create a subclass of Foo, and utilize fooOptionsObj to provide the options

class SubFoo extends Foo{
  subOptions: subFooOptionsObj;
  constructor(superOptions: fooOptionsObj, subOptions: subFooOptionsObj){
      super(superOptions);
      this.subOptions = subOptions;
  }
  draw(){
      drawSqaure(0, 0, this.subOptions.size, this.subOptions.size, this.subOptions.color);//assume this is a function
  }
}

interface subFooOptionsObj{
    color:string;
    size:number
}

If you wanted to create another one:

class DifferentSubFoo extends Foo{
...
}

You would have to pass different arguments into the superclass than in the subclass, leading to potential confusion.

The ideal solution would be to use only one argument, an options object, typed as whateverClassYourUsingOptionsObj. For instance:

class ThreeLetters{
  options: fooOptionsObj|barOptionsObj;
  constructor(options: fooOptionsObj|barOptionsObj){
    this.options = options;
  }
  sayMessage(){
    console.log(this.options.message)
  }
}

class Foo extends ThreeLetters{
  constructor(options: fooOptionsObj){
    super(options)
  }
  returnBol(): boolean{
    return this.options.bool;
  }
}

class Bar extends ThreeLetters{
  constructor(options: barOptionsObj){
    super(options);
  }
  sayNumber(){
    console.log(this.options.num);
  }
}

interface fooOptionsObj{
  message: string;
  bool: boolean;  
}
interface barOptionsObj{
  message: string;
  num: number;
}

However, there's a catch - because barOptionsObj lacks a bool, and fooOptionsObj lacks a num, even though they should not require both. This dilemma arises from the fact that within ThreeLetters, the options type is barOptionsObj|fooOptionsObj, despite the fact that it will be exclusively one or the other (per instance). How could I solve this issue, so that only one options object is necessary?

Answer №1

Given the code example provided, it seems like you aim to establish a valid subtype hierarchy within your class structure. The intention is to allow instances of Foo or Bar to be interchangeable with instances of ThreeLetters.

To achieve this, I suggest refining the type of the options property in each subclass by using the declare keyword for the property. This ensures that such information impacts only the static type system and does not affect the generated JavaScript code.

It might also be beneficial to broaden the base options type to something more general than the union of options from known subclasses unless there's a specific need to restrict subclasses to a predefined list of options types.

Below is the code snippet for the base class:

interface BaseOptions {
  message: string;
}

class ThreeLetters {
  readonly options: BaseOptions; // make it readonly to prevent unsound writes
  constructor(options: BaseOptions) {
    this.options = options;
  }
  sayMessage() {
    console.log(this.options.message)
  }
}

Note the use of readonly for the options property to prevent potential misuse scenarios where an instance could have its options overwritten:

const tl: ThreeLetters = new Foo({ message: "", bool: true });
const badFooOptions = { message: "", oopsie: 123 };
tl.options = badFooOptions; // error due to read-only nature of options

Now let's look at the subclasses:

interface FooOptionsObj {
  message: string;
  bool: boolean;
}
class Foo extends ThreeLetters {
  declare options: FooOptionsObj; // narrow down the type
  constructor(options: FooOptionsObj) {
    super(options)
  }
  returnBool(): boolean {
    return this.options.bool;
  }
}

And

interface BarOptionsObj {
  message: string;
  num: number;
}
class Bar extends ThreeLetters {
  declare options: BarOptionsObj; // specify the type
  constructor(options: BarOptionsObj) {
    super(options);
  }
  sayNumber() {
    console.log(this.options.num);
  }
}

There should be no errors now, and everything should function as expected. Good luck!

Playground link

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

Challenges encountered when testing middleware in a TypeScript Node.js Express project

I have been exploring the repository at https://github.com/goldbergyoni/nodebestpractices to enhance my understanding of nodejs best practices. Below is a middleware I developed: import { NextFunction, Request, Response } from "express"; import ...

The call does not match any overloads in Vue when using TypeScript

What is the reason behind the occurrence of the error in the ID part of the query? This call does not have a matching overload. <template> <swiper-slide slot="list" v-for="(list, index) in list.banner" :key=" ...

Concealing Angular Features Using a Button

Currently, I am experimenting with Angular and attempting to create a button that disappears when clicked. Despite trying methods like [hidden], (click)="showHide = !showHide", and several others, nothing seems to be working as expected. This is the curre ...

Using TypeScript and Node.js with Express; I encountered an issue where it was not possible to set a class property of a controller using

I have a Node application using Express that incorporates TypeScript with Babel. Recently, I attempted to create a UserController which includes a private property called _user: User and initialize it within the class constructor. However, every time I ru ...

Running a Jest test that triggers process.exit within an eternal setInterval loop with a latency of 0, all while utilizing Single

In the original project, there is a class that performs long-running tasks in separate processes on servers. These processes occasionally receive 'SIGINT' signals to stop, and I need to persist the state when this happens. To achieve this, I wrap ...

Executing JavaScript file using TypeScript code in Node.js

Is it possible to execute a JS file from TypeScript code in Node.js? One way to achieve this is by exposing the global scope and assigning values to it. For example: Global Scope (TypeScript): globalThis.names = ['Joe', 'Bob', 'J ...

Guide on setting default key/value state in TypeScript React application

Having the task of converting a React app to Typescript, I'm struggling to properly set the initial state of a hash object. Here is the original javascript code: export default class Wizard extends PureComponent { constructor(props) { su ...

Troubleshooting TypeScript Assertions with Record<number,number> Properties

After reading a blog, I came across the statement that T as K in TypeScript only works when T is subType of K or K is subType of T. I am unsure if this is true or not. When attempting to use as with properties defined as Record<T,K>, an error is thr ...

An issue has occurred in my deeply nested reactive form where I am unable to read the properties of null, specifically the 'controls' property

I have encountered an issue with a deeply nested form that communicates with a REST endpoint upon submission. While the first two levels of the form are functioning properly, I am facing numerous challenges when trying to work with the third level. One par ...

The collaboration of React hooks, typescript, mongoose, express, and socket.io in perfect harmony

I am currently working on setting up a frontend React app to communicate with a NodeJS Express API using socket.io import React, { useEffect, useState } from "react"; import io from "socket.io-client"; const socket = io("http://lo ...

Is it possible for TypeScript to automatically determine the type of an imported module based on its path?

I'm currently working on creating a function, test.isolated(), which wraps around jest.isolateModules. This function takes an array of strings representing the modules to be imported, along with the usual arguments (name, fn, timeout), and then inject ...

Executing Jest on every file within the imported Tree

Recently, I encountered a curious side effect and would appreciate the input of experienced members here. When executing the command npm run test -- --testPathPattern="filePath" --coverage, I receive coverage information like this - Statemen ...

The compiler error TS2304 is indicating that it cannot locate the declaration for the term 'OnInit'

I successfully completed the Angular superhero tutorial and everything was functioning properly. However, when I close the CMD window running NPM and then reopen a new CMD window to reissue the NPM START command, I encounter two errors: src/app/DashBoard ...

What method does Angular use to distinguish between familiar elements and unfamiliar ones?

<bar>- it is a tag with a random name, not officially recognized, so it makes sense that it is considered invalid <div> is valid because it is a standard HTML element - does Angular have a documented list of standard elements? Where can I fin ...

What is the best way to incorporate additional data into a TypeScript object that is structured as JSON?

I'm exploring ways to add more elements to an object, but I'm uncertain about the process. My attempts to push data into the object have been unsuccessful. people = [{ name: 'robert', year: 1993 }]; //I aim to achieve this peopl ...

Utilizing Express-WS app and TypeScript to manage sessions

I am currently working on setting up a node server using Typescript with the help of express and express-ws. My goal is to include Sessions in my application, so I have implemented express-session. Below you can find some pseudo code: import * as session ...

Utilizing useClass in Angular's APP_INITIALIZER

Before my application starts up, I require some API data and am currently handling it with APP_INITIALIZER and useFactory. However, I aim to enhance the structure by separating all the code from app.module.ts: app.module.ts import { NgModule} from '@ ...

Encountered an error when creating my own AngularJS module: Unable to instantiate

Attempting to dive into TypeScript and AngularJS, I encountered a perplexing error after following a tutorial for just a few lines. It appears that there may be an issue with my mydModule? angular.js:68 Uncaught Error: [$injector:modulerr] Failed to inst ...

Adjust property value based on changes in a related property

Currently, I am developing a TypeScript-powered Angular (5) application and I have encountered a puzzling question that is proving elusive to solve. Let me illustrate with the following example: ... export class SomeComponent implements onInit { ... ...

"Unsubscribing in Angular via a button click: A step-by

I'm having trouble canceling a subscription for "device orientation" in Angular using (click) in HTML. I've tried multiple solutions but none seem to work. Does anyone have any ideas on how to fix this? TS // Watching the change in device compa ...