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

Populate an HTML table using a JavaScript array containing objects

Greetings, fellow coders! I am new to the coding world and this community, and despite my efforts in searching for a solution, I couldn't find exactly what I was looking for. So, here is my question: I have an array structured as follows: const arr ...

Basic inquiries concerning Vue.js and JavaScript

Hey there, I recently developed a small app to practice my Vue skills. However, there are a few features that I would like to implement but I'm not sure how to do it just yet. <div class="container" id="app"> <div class="row"> <d ...

JavaScript: Toggle between 2 functions using a single click event listener

I am facing an issue with coding a Sidebar that features an animated Burger Menu Button named "navicon1". The Menu Button utilizes the "open" class to create a cool animation effect. Moreover, I aim to have the functions "openNav" and "closeNav" toggled wh ...

Access the child element of a span by targeting a specific attribute value using Protractor

Trying to check if a popover appears using protractor. Below is the snippet of HTML code with the popover in the last child span: <span tariff-popover="views/popovers/c2g/airport.html" class="ng-isolate-scope"> <span ng-transclude=""> ...

The toggle button requires two clicks to activate

I created a toggle button to display some navigation links on mobile screens, but it requires two clicks upon initial page load. After the first click, however, it functions correctly. How can I ensure that it operates properly from the start? Below is t ...

Tips for preventing tiny separation lines from appearing above and below unordered list elements

I am attempting to utilize twitter bootstrap to create a select-option style list. How can I eliminate the thin separation lines above and below the list of items? Refer to the screenshot: Below is the visible code snippet. It would be best to view the j ...

Firebase's equalTo function seems to be malfunctioning

Having encountered an issue with Firebase, I am currently attempting to fetch all of my posts in JavaScript. Specifically, I am looking for posts in the correct language that are marked as "published" and sorted by their published date. In my Firebase dat ...

I am trying to figure out how to properly utilize server-only functions within Next.js middleware

In my current project, I am utilizing Next.js 13 along with the App Router feature. While attempting to include a server-specific fetch function in middleware.js, an error message is encountered: Error: Unable to import this module from a Client Compone ...

What is the most efficient method for creating and adding an element in jQuery?

When it comes to appending div elements to a page, there are different approaches that can be taken. Let's explore two methods: $('#page123').append("<div id='foo' class='checkbox' data-quesid='foofaa'>&l ...

Issues with loading NextJS videos can occur when accessing a page through a link, as the videos may fail to load without

Issue Greetings. The problem arises when attempting to load muse.ai videos on-page, specifically when accessing the page with a video embedded through a NextJS link. To showcase this issue, I have provided a minimal reproducible example on StackBlitz her ...

Unexpected value assigned to private variable within a PHP class

Initially, the issue I am encountering originates from my PHP class that is called by a PHP file accessed through an AJAX call. The main problem lies in the fact that the return value does not align with the sybase_result value. What could possibly be mis ...

Issue: Unable to locate a differ that supports the object '[object Object]' of type 'object'. NgFor can only bind to Iterables like Arrays

I have successfully pulled data from the jsonplaceholder fake API and now I am attempting to bind it using Angular 2 {{}} syntax. However, I encountered an error that states: "Error: Cannot find a differ supporting object '[object Object]' of typ ...

What is the advantage of utilizing the ng-idle library for monitoring user idle status when we have the ability to create custom JavaScript code to track inactivity based on keyboard and mouse events?

I have implemented a method to detect user idle time using mouse and key events as shown below. @HostListener('window:keydown', ['$event']) @HostListener('window:mousedown', ['$event']) @HostListener('window:mou ...

Discover how to achieve the detail page view in Vue Js by clicking on an input field

I'm a beginner with Vuejs and I'm trying to display the detail page view when I click on an input field. <div class="form-group row"> <label for="name" class="col-sm-2 col-form-label">Name</label> ...

Issue with importing MomentJS globally in TypeScript

When it comes to defining global external modules in TypeScript, there is a useful option available. For instance, if you have jQuery library loaded externally, you can set up a global definition without having to include its duplicate in the TypeScript bu ...

eliminate the offspring of a component (chessboard)

Hey there! I'm currently working on developing a chess game and I could really use your expertise to help me solve an issue. In my code, when I try to move a piece in the game, this is what happens: 1. First, I remove the existing piece from its cu ...

Encountering an issue with my Discord bot where it displays the error message "Unable to access property 'cache' of an undefined object"

I encountered an issue while setting up discord-xp for my bot. Whenever I attempted to use the leaderboard command, an error message popped up: username: client.users.cache.get(key.userID) ? client.users.cache.get(key.userID).username : "Unknown" ...

In Vue.js, when attempting to arrange an array of objects in descending order based on a specific key (such as "name"), the intention is to prioritize data containing uppercase letters to be displayed

I am struggling to organize an array of objects based on a specific key (name). My goal is to have the data with uppercase letters appear first, but for some reason, it's displaying the lowercase data first. I've been using the lodash method "ord ...

What causes the truncation of the backslash in the string "videos1_visualisation.mp4"?

Check out this example of AngularJS code I've created. The factory contains a list of video sources. var videoPlayer=angular.module('videoPlayer',[]) videoPlayer.controller("videoplayer",["$scope","videolist",function($scope,videolist) ...

Using conditional statements like 'if' and 'else' in JavaScript can

Can someone help me with solving my problem using if-else statements in Javascript? I need to filter names by gender and save them as keys - woman / man in local storage. Any assistance would be greatly appreciated. I am struggling to figure out how to im ...