What is the reason behind Typescript executing the abstract class before anything else?

I'm currently facing a challenge solving an abstract class problem with Typescript. Let me explain what I am trying to accomplish.

There is a class named Sword that extends Weapon. Each Weapon must have certain properties like the damage, but since each weapon type inflicts different levels of damage (for example, a sword may deal 1 damage while a bow deals 2 damage), I need to define specific properties in the Sword class. Here's how my script looks:

abstract class Weapon
{
    protected abstract damage: number;
    constructor() {
        alert(this.damage);
    }

    showDamage() {
        alert(this.damage);
    }
}

class Sword extends Weapon implements WeaponInterface {
    protected damage: number = 999;

    constructor() {
        super();
    }
}

const sword = new Sword;
sword.showDamage();

When running this script on

http://www.typescriptlang.org/play/
, I receive two messages:

undefined
999

I'm unsure why the Weapon.constructor gets executed first. This seems to defeat the purpose of declaring an abstract value. If I have to use super(this.damage) to pass it into the Weapon class, there doesn't seem to be a need for protected abstract damage.

If I can't even establish basic inheritance in Typescript, why does it offer support for abstract classes? It forces me to do new Weapon(new Sword), making it impossible to typehint a SwordInterface on other classes like Inventory.

class Inventory
{
    // Assuming we are equipped with a "Shield," we can only equip items of type "Sword"
    addSword(sword: SwordInterface): void {

    }
}

As someone new to compiled languages and Typescript, I'm seeking guidance on the proper way to achieve this without resorting to passing class properties into the super() call.

I want to maintain inheritance and interfaces without any disruptions.

Answer №1

When it comes to Typescript, property initialization in the class body is not treated in a special way; it is done as part of the class constructor. However, one could envision a language where this assignment happens very early, before any constructor is executed. Typescript does not follow this approach, likely because it may not be the most straightforward and coherent method.

class Sword extends Weapon implements WeaponInterface {
    protected damage: number = 999;

Therefore, you need to take matters into your own hands. One approach to accomplish your desired outcome is by splitting your code in constructors into two parts: one for initializing variables and the other for executing the remaining logic. This strategy is sometimes referred to as two-phase initialization:

abstract class Weapon
{
    protected abstract damage: number;

    // NOTE: abstract properties must be initialized by subclasses
    //     in initialize() because they are used here in Weapon class constructor
    protected initialize(): void { }

    constructor() {
        this.initialize();
        alert(this.damage);
    }

    showDamage() {
        alert(this.damage);
    }
}

class Sword extends Weapon  {
    protected damage: number;

    protected initialize(): void {
        super.initialize();
        this.damage = 999;
    }

    constructor() {
        super();
    }
}

const sword = new Sword;
sword.showDamage(); // shows 999 twice

Answer №2

It's a given that base class constructors will always run before derived class constructors. Can you imagine any other scenario?

class Base {
  damage = 12;
}

class Derived extends Base {
  constructor() {
    // Do you expect this to output 'undefined'?
    console.log(this.damage);
  }
}

Answer №3

Prior to setting the damage property, the base constructor is executed first in this scenario. Here is the constructor for the Sword class:

function Sword() {
    var _this = _super.call(this) || this;
    _this.damage = 999;
    return _this;
}

An alternative approach would be to pass the damage as a parameter to the base constructor:

abstract class Weapon {

    protected damage: number;

    constructor(damage: number) {
        this.damage = damage;
        alert(this.damage);
    }

    showDamage() { /*...*/ }
}

class Sword extends Weapon  {

    constructor() {
        super(999);
    }
}

A more concise version could look like this:

abstract class Weapon {
    constructor(protected damage: number) {
        alert(this.damage);
    }

    showDamage() { /* ... */ }
}

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

Converting the following ngRx selector to a boolean return – a guide

I am currently using the following filter to retrieve a list of data within a specific date range. I have implemented logic in the component where I check the length of the list and assign True or False to a variable based on that. I am wondering if ther ...

Tips for customizing the font color in Material UI Typography

Is it possible to change the color of only this text to red? return <Typography style={{ color: 'red' }}>Login Invalid</Typography> I came across this online solution, but I am unsure how to implement it as there is no theme={color ...

Triggering an event in Angular 2 after ngFor loop completes

I am currently attempting to utilize a jQuery plugin that replaces the default scrollbar within dynamic elements in Angular 2. These elements are generated using an ngFor loop, making it impossible to directly attach jQuery events to them. At some point, ...

No declaration file was found for the module named 'vue2-timepicker'

Overview I integrated the vue2-timepicker plugin into my Typescript/Nuxt.js application. However, I encountered an issue with importing vue2-timepicker. import timepicker from 'vue2-timepicker' Could not find a declaration file for module &apos ...

The field '_id' is not present in the type Pick

I'm working on a project using Next.js and attempting to query MongoDB with TypeScript and mongoose, but I keep encountering a type error. types.d.ts type dbPost = { _id: string user: { uid: string name: string avatar: string } po ...

Resolving the error "Property not found on type 'any[]' after retrieving an object from the database in Typescript"

Being a beginner in the world of TypeScript, I am struggling to comprehend the error message and how to resolve it. This is the snippet of my code: app.put('/compareSpread' , async (req , res) => { const { roundedSpreadPercentage , cropId} ...

Tips for incorporating Material UI Icon v1.0.0-beta.36 into a .tsx component

Currently utilizing material-ui-icons v1.0.0-beta.36. I am endeavoring to incorporate a Search icon within a .tsx component. .tsx component: import React, { Component, ReactElement } from 'react' import Search from 'material-ui-icons/Sear ...

What is the best way to assign table rows to various interfaces in typescript?

Assuming I have the interfaces provided below: export interface IUserRow { id: string, state: string, email: string, } export interface ITableRow { id: string, [key: string]: any; } export type Rows = ITableRow | IUserRow; // additio ...

NextJS Typescript Layout is throwing errors due to the absence of required props

After following the instructions on https://nextjs.org/docs/basic-features/layouts#with-typescript and making changes to my Home page as well as _app.tsx, I encountered an issue with the layout file Layout.tsx. The provided guide did not include an exampl ...

Using TypeScript, pass an image as a prop in a Styled Component

I am facing an issue with the code below that is supposed to display the "NoBillsLaptopPNG.src" image on the screen, but for some reason, the image is not showing up. The images are being imported correctly, so I'm unsure why the image is not appeari ...

Angular 5 - Creating a dynamic function that generates a different dynamic function

One of my latest projects involved creating a versatile function that generates switch-case statements dynamically. export function generateReducer(initialState, reducerName: ReducerName, adapter: EntityAdapter<any>): (state, initialState) => ISt ...

Issue with Formik compatibility in Next JS 14 Application Structure

I attempted to create a basic validation form using Formik. I meticulously followed their tutorial and example, but unfortunately, the form is not functioning correctly. Despite my efforts, I have been unable to identify a solution (Please correct me if I& ...

Customizing MUI DataGrid: Implementing unique event listeners like `rowDragStart` or `rowDragOver`

Looking to enhance MUI DataGrid's functionality by adding custom event listeners like rowDragStart or rowDragOver? Unfortunately, DataGrid doesn't have predefined props for these specific events. To learn more, check out the official documentati ...

Creating an npm library using TypeScript model classes: A step-by-step guide

Currently, I am working on a large-scale web application that consists of multiple modules and repositories. Each module is being developed as an individual Angular project. These Angular projects have some shared UI components, services, and models which ...

Error with React Query Mutation and TypeScript: The argument '{ surgeryID: any; stageTitle: any; }' cannot be assigned to a parameter of type 'void'

Utilizing react-query for fetching and posting data to my database on supabase has been really helpful. I took the initiative to create a custom hook specifically for adding records using react-query: export function useAddSurgeryStage() { const { mutate ...

Typescript implementation for a website featuring a single overarching file alongside separate files for each individual webpage

Although I've never ventured into the realm of Typescript before, I am intrigued by its concept of "stricter JS". My knowledge on the subject is currently very limited as I am just starting to experiment with it. Essentially, I have developed my own ...

The functionality of allowEmpty : true in gulp 4.0 does not seem to be effective when dealing with

gulp.task("f1", () => { gulp.src([], {"allowEmpty": true}) .pipe(gulp.dest(location)); }) An error message pops up saying "Invalid glob argument:" when the code above is used. gulp.task("f1", () => { gulp.sr ...

Determine the class of an object within the "keyof" parameter by utilizing both property and generic types

I have a requirement to create an interface with generic types that can accept an object with keys representing "root field names" and values as arrays of objects defining sub-fields with the key as the name of the sub-field and the type as the value' ...

Creating HTML elements dynamically based on the value of a prop within a React component

In my React component built using Typescript, it takes in three props: type, className, and children The main purpose of this component is to return an HTML element based on the value passed through type. Below is the code for the component: import React ...

The received data object appears to be currently undefined

I am currently using MapBox to display multiple coordinates on a map, but I am encountering an issue where the longitude and latitude values in my return object are showing up as undefined. Could there be a missing configuration that is preventing the data ...