Strategies for preventing multi-level inheritance of TypeScript class properties and methods

In my current JavaScript class structure, the DataService is defined as follows:

// data.service.ts
export class DataService {
   public url = environment.url;

   constructor(
       private uri: string,
       private httpClient: HttpClient,
   ) { }

   getAll() {}

   getOne(id: number) {}

   create(data: any) {}

   // etc...
}

Following this, there is a general data model that utilizes the methods of DataService to interact with the server:

// Model.model.ts
import './data.service';

export class Model extends DataService {
    all() {}

    get() {
        // parse and perform basic validation on the DataService.getOne() JSON result
    }

    // etc...
}

Lastly, I have created a specific data model based on Model.model.ts named User.model.ts:

// User.model.ts
import './Model.model.ts';

export class User extends Model {
    id: number;
    name: string;
    email: string;

    init() {
        // implement specific validation on Model.get() result
    }
}

When utilizing the User class in my code, it allows for direct calling of the DataService's getAll() function. However, this can lead to bypassing the built-in validations.

I am interested in blocking method inheritance within a class. Is there a way to achieve this in JavaScript similar to PHP's static method functionality?

I envision a scenario where:

const dataService = new DataService();
dataService.getAll(); // returns void

const model = new Model();
model.getAll(); // returns undefined
model.all();    // returns void

const user = new User();
user.getAll();  // returns undefined
user.all();     // returns void

Is there a workaround or approach to restrict method inheritance like this?

Answer №1

To avoid it from being constructed when called, simply include the private keyword in the function definition like this: private getAll() {}. However, keep in mind that private is a feature of TypeScript, not JavaScript. Even if you attempt to enforce the prevention of construction, it can still be invoked. At present, there is no foolproof method to completely prevent its construction.

If you want to prevent it in TypeScript, using the private keyword should suffice. Just be aware that it will not throw an undefined result as anticipated. Alternatively, you could replace the function with one that returns undefined in child classes.

Answer №2

After thoroughly analyzing your code and use case, it seems that the optimal way to achieve the desired behavior is to reconsider the structure of inheritance between Model and DataService. It is essential to adhere to the Liskov substitution principle, which dictates that if Model extends DataService, then a Model instance should be treated identically to a DataService instance.

Rather than having Model directly extend DataService, consider a different approach:

// Defining the base parent class for both DataService and Model/User
class BaseDataService {
  getOne(id: number) { }
  create(data: any) { }
}

// Subclass with getAll() method
class DataService extends BaseDataService {
  getAll() {}
}

// Subclass without getAll() method
class Model extends BaseDataService {
  all() { }
  get() { }
}

// User class extending Model
class User extends Model {
  id!: number;
  name!: string;
  email!: string;
  init() { }
}

const dataService = new DataService();
dataService.getAll(); // void

const model = new Model();
model.getAll(); // error
model.all();    // okay

const user = new User();
user.getAll();  // error
user.all();     // okay

This revised structure aligns with your requirements seamlessly. Does this solution meet your needs effectively?


It's possible that you may encounter issues when attempting to call this.getAll() within the implementation of Model or User, especially within the empty body of the all() method in Model. If this functionality is vital to your application despite not being explicitly mentioned in the original question, additional adjustments are necessary while preserving the substitution principle.

To address this, consider making getAll() a protected method and exposing an all() method in DataService:

class DataService {
  getOne(id: number) { }
  create(data: any) { }
  protected getAll() { }
  all() { 
    this.getAll();
  }
}

class Model extends DataService {
  all() {
    this.getAll();
  }
  get() { }
}

class User extends Model {
  id!: number;
  name!: string;
  email!: string;
  init() { }
}

const dataService = new DataService();
dataService.getAll(); // error
dataService.all(); // okay

const model = new Model();
model.getAll(); // error
model.all();    // okay

const user = new User();
user.getAll();  // error
user.all();     // okay

In this scenario, getAll() serves as an internal method meant solely for internal usage. Hopefully, one of these approaches proves beneficial to your project. Best of luck!

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

What are the reasons behind memory leaks and decreased rendering speed when I exit and then reopen a React component (specifically Material-Table)?

I have been working on a basic React example for learning purposes, and I incorporated the use of material-table in one of my components. However, I noticed that each time I change pages and reopen the component (unmount and mount), the rendering speed of ...

Oops! There seems to be a problem with the Node.js App. The MongooseError is indicating that the parameter `uri` in the `openUri()` function should be a string,

I've encountered an issue with my Next.js app involving MongoDB that I've been struggling to resolve. Hoping someone here can provide some insight and help me out. This is quite crucial for my project. First, I'll share the code from my serv ...

Get Subject Alternative Name from X.509 in a Node.js environment

Having trouble retrieving the SAN from a DoD Smart Card, as the subject alternative name is returning othername:<unsupported>. I have not been able to find many examples on how to parse this information. I would have thought that X509 li in node woul ...

Difficulty encountered when applying a CSS class with JavaScript

The Javascript function I am currently using is able to select multiple links. This behavior occurs because of the Regular expression I applied with the '^' symbol. I decided to use this approach because my links are in the following format: htt ...

How can you create a table cell that is only partially editable while still allowing inline JavaScript functions to work?

Just a few days back, I posted a question similar to this one and received an incredibly helpful response. However, my attempt at creating a table calculator has hit a snag. Each table row in a particular column already has an assigned `id` to transform t ...

How can Vue detect modifications made in the edited field?

I am facing an issue with tracking changes in a specific object. Here is the structure of the object: users: { email: '', password: '' } My goal is to detect any edits made to the keys within the users object and store the key ...

Retrieve the specific item from an object array using the designated key

I am working with an array of objects and my goal is to retrieve the object that has the key "Z". While I could iterate through the array, checking each key one by one until finding a match, I believe there might be a more efficient solution than my curre ...

Customize the appearance of your apps script using conditional formatting to highlight values that are

https://i.stack.imgur.com/L1KFZ.png I need to create an array of all 50 US states based on the abbreviations in a column. The goal is to compare each cell against the array and if it doesn't match, format it red. After researching conditional format ...

Converting PHP code to Typescript

Currently, I am working on developing a function for firebase that will trigger a POST request to call a specific URL. While I have successfully implemented GET requests, tackling the POST method has proven to be more challenging. I have some sample code ...

Allow Microsoft OAuth authentication for web applications only, restricting access to other Microsoft services

I am currently integrated Firebase into my website, allowing users to sign in using their Microsoft accounts through OAuth 2.0: import {getAuth, signInWithRedirect, OAuthProvider} from "firebase/auth"; (...) const provider = new OAuthProvider(& ...

Next.js (TypeScript) - Error: Property 'req' is not recognized on the type 'GetServerSideProps'

Currently, I am tackling a challenge involving the utilization of next-auth and attempting to access the session from within getServerSideProps. However, in order to achieve this, it is essential for me to provide the context request and context response ...

What is the solution for fixing the '$ not defined error' in a .js file with $ajax code?

var example = document.createElement("SCRIPT"); example.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"; var nodeScript= document.createTextNode("function test() {console.log('test message');$.ajax({ type: \"POST&bs ...

Hovers and click effects for navigating through images

The website I'm currently working on is stipz.50webs.com. p.s. HOME functionality is not active at the moment. Having successfully implemented the onhover and onmouseout features, my next goal is to enhance the navigation effects for each div/img so ...

Ways to update all URLs on a page with ajax functionality

My userscript is designed to modify the href of specific links on an IP-direct Google search page: // ==UserScript== // @name _Modify select Google search links // @include http://YOUR_SERVER.COM/YOUR_PATH/* // @include http://62.0.54.118/* // ==/Us ...

Can I retrieve the count of pixels below a certain threshold value using WebGL/OpenGL?

class WebGLProcessor { initializeGLContext = (canvas, version) => { canvas.width = window.innerWidth * 0.99; canvas.height = window.innerHeight * 0.85; var gl = canvas.getContext(version ? 'webgl' : 'webgl2&apo ...

Tips for sharing the scope using module.exports

Within handler.js, I have exported 2 functions: one for initialize() and the other for handle(). The initialize function is used to dynamically load the handler based on the application settings. I have a shared variable called var handler outside the modu ...

The error message "Uncaught TypeError: Unable to retrieve the 'length' property of an undefined object in Titanium" has occurred

Here is the data I am working with: {"todo":[{"todo":"Khaleeq Raza"},{"todo":"Ateeq Raza"}]} This is my current code snippet: var dataArray = []; var client = new XMLHttpRequest(); client.open("GET", "http://192.168.10.109/read_todo_list.php", true); c ...

"Troubleshooting: Why is my AngularJS ng-repeat failing to

I recently started delving into AngularJS, but I've hit a roadblock with a particular issue. For some reason, the ng-repeat directive isn't iterating through the users array as expected. The resulting list is just blank. Below is a snippet of my ...

What is the best way to include an icon before each option in a VuetifyJS combobox?

I'm looking to enhance the combobox feature in VuetifyJS by adding an icon before each option in the dropdown menu. Can someone guide me on how to achieve this functionality? You can check out a sample of the combobox on CodePen here: https://codepen. ...

The datepicker in Angular UI is displaying incorrect dates

Currently, I am developing an application using Angular and incorporating Angular UI. One of the features I have implemented is a datepicker that is coded like this: <input type="text" ng-required="true" name="targetDate" uib-date ...