Designate as a customizable class property

I'm struggling to create a versatile inheritance class for my services. Currently, I have two service classes named Service_A and Service_B that essentially do the same thing. However, Service_A works with Class_A while Service_B works with Class_B. The only difference between them is the type they work with.

My goal is to consolidate the code into a more generic class (EntityService) that both Service_A and Service_B can inherit from. This way, I can write the code once and reuse it without having to override all methods or specify types in each method call.

Here's how EntityService looks like:

declare class Observable<T> { x: T }

declare class Actor {
    doStuff<T>(anything: T): T[];
    getData<I>(): I;
    getObservable<U, I>(data: I): Observable<U[]>;
}

interface Entity {
    param_1: number,
    param_2: number,
}

class EntityObj {
    id: string;
    param_1: number;
    param_2: number;

    constructor(id: string, param_1: number, param_2: number) {
        this.id = id;
        this.param_1 = param_1;
        this.param_2 = param_2;
    }

    static fromInterface(entity: Entity, id: string): EntityObj {
            return new this(id, entity.param_1, entity.param_2);
    }
}

export class EntityService {

    /* Desired functionality:
     * static classTypeAlias: EntityObj;
     * static interfaceTypeAlias: Entity;
     */

    constructor(private readonly actor: Actor) {}

    getEntityObjs(): Observable<EntityObj[]> | null {
        let data: Entity = this.actor.getData()
        let entityObjs: Observable<EntityObj[]> = this.actor.getObservable<EntityObj, Entity>(data);
        return entityObjs;
    }

    getObjFromInterface(): EntityObj {
        let entityObj: EntityObj = EntityObj.fromInterface(this.actor.getData(), "foo");
        return entityObj;
    }

    update(entity: EntityObj): void { let entityDoc = this.actor.doStuff<Entity>(entity); }
}

I want Service_A to be able to use methods like update() with a parameter of type "Class_A" implicitly, and getEntityObjs() to return something of type "Observable<Class_A[]> | null" implicitly as well.

I tried exploring generic types but couldn't figure out how to avoid extending them from EntityObj when accessing properties or using their values associated with the class (like

class EntityService<classType, interfaceType> {...}
) to define argument/return types.

If you have any advice or suggestions on how to solve this issue using proper techniques, please share them. Your help would be greatly appreciated!

Answer №1

If you wish to parameterize subclasses of EntityService with different types for classTypeAlias and interfaceTypeAlias, the simplest way is to make EntityClass generic in type parameters corresponding to these types, denoted as C and I:

export class EntityService<C, I> {
  ⋯
}

To achieve this, replace every instance of EntityObj with C and Entity with I within EntityService:

export class EntityService<C, I> {

  constructor(private readonly actor: Actor) { }

  getEntityObjs(): Observable<C[]> | null {
    let data: I = this.actor.getData()
    let entityObjs: Observable<C[]> = this.actor.getObservable<C, I>(data);
    return entityObjs;
  }

  getObjFromInterface(): C {
    let entityObj: C = C.fromInterface(this.actor.getData(), "foo"); // error
    return entityObj;
  }

  update(entity: C): void {
    let entityDoc = this.actor.doStuff<I>(entity); // error
  }

}

There are two errors to fix.

The first error occurs because we call doStuff<I>(entity) but pass an entity of type C. To address this, either constrain C to extend

I</code, modify the call to be <code>doStuff<C>(entity)
.

update(entity: C): void {
  let entityDoc = this.actor.doStuff<C>(entity); // okay
}

The second error arises from replacing EntityObj the class constructor with C which is a type. To resolve this, EntityService needs access to a class constructor value of the appropriate type. This requires passing such an object when constructing instances of EntityService:

export class EntityService<C, I> {

  constructor(
    private readonly actor: Actor,
    public ctor: { fromInterface(e: I, id: string): C } // add it
  ) { }

  getEntityObjs(): Observable<C[]> | null {
    let data: I = this.actor.getData<I>()
    let entityObjs: Observable<C[]> = this.actor.getObservable<C, I>(data);
    return entityObjs;
  }

  getObjFromInterface(): C {
    let entityObj: C = this.ctor.fromInterface(this.actor.getData(), "foo");
    return entityObj;
  }

  update(entity: C): void { let entityDoc = this.actor.doStuff<C>(entity); }
}

Now that it's compiling, subclass with specific C and I type arguments and provide the ctor during construction:

class MainEntityService extends EntityService<EntityObj, Entity> {
  constructor(actor: Actor) {
    super(actor, EntityObj);
  }
}

Use it like this:

new MainEntityService(new Actor()).getEntityObjs()
// Observable<EntityObj[]> | null

Another example subclass:

interface Foo {
  a: string;
  b: number;
}

class Bar implements Foo {
  ...
}

class SomeOtherThing extends EntityService<Bar, Foo> {
  constructor(actor: Actor) { super(actor, Bar) }
}
new SomeOtherThing(new Actor()).getEntityObjs()
// Observable<Bar[]> | null

This method works, though there are alternative approaches depending on your requirements.

Playground link to code

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

I'm having trouble importing sqlite3 and knex-js into my Electron React application

Whenever I try to import sqlite3 to test my database connection, I encounter an error. Upon inspecting the development tools, I came across the following error message: Uncaught ReferenceError: require is not defined at Object.path (external "path ...

Excluding properties based on type in Typescript using the Omit or Exclude utility types

I am looking to create a new type that selectively inherits properties from a parent type based on the data types of those properties. For instance, I aim to define a Post type that comprises only string values. type Post = { id: string; title: string ...

Whenever I attempt to host my Node.js app using the GCP deploy command, it fails to work properly. The error message that appears states: "Module 'express' cannot be found."

My NodeJS application is written in TypeScript and utilizes the Express framework. I'm looking to host it on the GCP cloud using the gcloud app deploy command. First, I compile my TS sources to JavaScript - is this the correct approach? Afterwards, I ...

Issues may arise in TypeScript when you are working with an array of objects along with other properties within a type

I am encountering an issue with an object structure similar to the one below: let Obj = { ['0'] : { mode: 'x' }, getMode: () => 'x' } The problem arises when I attempt to create a type definition as shown here: type Obj = ...

The issue encountered in Cocos Creator 3.8 is the error message "FBInstant games SDK throws an error stating 'FBInstant' name cannot be found.ts(2304)"

Encountering the error "Cannot find name 'FBInstant'.ts(2304)" while using FBInstant games SDK in Cocos Creator 3.8. Attempting to resolve by following a guide: The guide states: "Cocos Creator simplifies the process for users: ...

Issue with FormControlLabel not properly implementing disableCloseOnSelect in Material UI v5 Autocomplete

I'm currently working on developing a wrapper for the MUI Autocomplete component. If you want to see my progress, feel free to check out my codesandbox link: here To keep things simple for demonstration purposes, I've significantly simplified t ...

Differences between ng build --prod and ng --build aot in Angular 7

Recently, I successfully built an Angular 7 application using the command ng build --prod. However, I am now facing a dilemma regarding ng build --aot versus ng build --prod. Our application is currently deployed on ..., and although it runs successfully ...

atom-typescript encounters difficulty locating typings

After setting up a new Angular/Typescript project in Atom with atom-typescript, I encountered an issue. The main angular module file imports all modules and type definition files, but errors are now showing in my .ts files. This is due to atom-typescript n ...

Guide on importing videojs-offset library

I am working on a component that utilizes video.js and HLS streaming in Angular. The component code is as follows: import { Component, ElementRef, AfterViewInit, ViewChild, Input, EventEmitter, Output } from '@angular/core'; import ...

A step-by-step guide on incorporating a .env file into a React JS project that is written with TypeScript

I'm currently working on a React JS project with TypeScript. I know that we can utilize a .env file to define some configurations, like so: .env file REACT_APP_SOME_CONFIGURATION = "some value" We can then use these configurations in our c ...

Can you share the appropriate tsconfig.json configuration for a service worker implementation?

Simply put: TypeScript's lib: ['DOM'] does not incorporate Service Worker types, despite @types/service_worker_api indicating otherwise. I have a functional TypeScript service worker. The only issue is that I need to use // @ts-nocheck at t ...

No response from NgClass after executing the function

In my NgClass function, I make use of an array that is populated in the ngOnInit lifecycle hook. Within ngOnInit, the prepareRezerwation() function creates a variable called colorRezerwation: this.nodeService.getRezerwations(this.minMax).subscribe(re ...

Encountering a type error in Typescript when assigning a transition component to a Material UI Snackbar

Attempting to implement snackbar alert messages using Material UI in a React JS application with TypeScript. Encountering a type error when trying to modify the transition direction of the snackbar. Referenced the snackbar demo from Material UI documentat ...

Unable to modify the value of a key within an object using TypeScript

I'm struggling to update the value of a key within an object using TypeScript. Here's an overview of the types I'm working with: export enum BAR_TYPES { apple = "apple", banana = "banana" } export type BarTypes = ...

Having trouble opening a JPEG file that was generated using the Writefile Api in Ionic-Cordova

Currently, I am using the writeFile API to create a JPEG image. The process is successful and the image is stored in the directory as expected. However, when I try to open the file manually from the directory, I encounter an error message saying "Oops! Cou ...

How do I make functions from a specific namespace in a handwritten d.ts file accessible at the module root level?

Currently, I am working on a repository that consists entirely of JavaScript code but also includes handwritten type declarations (automerge/index.d.ts). The setup of the codebase includes a Frontend and a Backend, along with a public API that offers some ...

Angular Service singleton constructor being invoked multiple times

I have been facing an issue with using an app-wide service called UserService to store authenticated user details. The problem is that UserService is being instantiated per route rather than shared across routes. To address this, I decided to create a Core ...

In what way can a piped operator in rxjs be influenced by the value returned by a subsequent operator?

When attempting to examine values between operators in an rxjs pipe, I decided to use tap to log them to the console. I added two taps, one before a map operator used for sorting an Array, and one after. Surprisingly, both taps logged the same sorted Arra ...

Tips for configuring identical libraries under different names

As a Japanese web developer, I struggle with my English skills, so please bear with me. Currently, I am utilizing an npm library. I have forked the library and made some modifications to it. In order to incorporate these changes, I updated my package.js ...

Ensure that the interface limits the key value to match precisely the value of a constant in Typescript

Seeking assistance in understanding how to enforce a specific type for an optional key within an interface: const FIRST = "FIRST" const SECOND = "SECOND" interface TSomeInterface { element: Element order?: typeof FIRST | typeof ...