Properties of a child class are unable to be set from the constructor of the parent class

In my current Next.js project, I am utilizing the following code snippet and experiencing an issue where only n1 is logged:

class A { // A: Model
    constructor(source){
        Object.keys(source)
        .forEach(key => {
            if(!this[key]){
                this[key] = source[key];
            }
        });
    }
}

class B extends A{ // B: User
    first_name: string;
}

let v = new B({first_name: "nof"});
console.log(v)
// B { first_name: undefined }

Strangely enough, when I use this code in a non-Next.js project, both n1 and n2 are displayed. It seems like there may be some configurations within Next.js causing this discrepancy. Any suggestions on how to achieve consistent behavior without declaring a constructor in the B class?


The original example for reference:

class A {
    constructor(){
        this.init()
    }
    init(){
        this["n1"] = "nof";
        this["n2"] = "nof";
    }
}

class B extends A{
    n1: string;
}

let v = new B();
console.log(v)
// B { n1: undefined, n2: 'foo' }

Answer №1

The outcome meets expectations. When creating a fresh instance of B, it initiates the creation of A first so that both n1 and n2 are initialized as 'nof'. Subsequently, B is created and replaces n1 with an unset (typically undefined) variable, resulting in n1 = undefined while n2 should remain as 'nof'.

Answer №2

The change in behavior you are experiencing is likely due to the useDefineForClassFields setting being enabled now, whereas it was disabled before. According to the provided link:

Use Define For Class Fields - useDefineForClassFields

This flag is used when transitioning to the new standard version of class fields. TypeScript implemented class fields long before they were officially approved by TC39. The latest version of the upcoming specification has a different runtime behavior compared to TypeScript’s implementation but uses the same syntax.

This flag aligns with the upcoming ECMA runtime behavior.

You can find more information about this transition in the 3.7 release notes.

Essentially, after the constructor for A finishes running, the (implicit) constructor of B redefines the property, replacing the one defined by A and losing the value created by A in the process. This behavior mirrors JavaScript's behavior as described above.

To resolve this issue, you can disable the setting to achieve the desired output from your code; playground link (note: other type errors have not been fixed). However, it is recommended to update your code instead. From a design perspective, A should not be initializing properties that it does not define. It is also not safe to assign all properties from an object to the instance being initialized.

One solution is to write constructors that explicitly copy the intended data. This approach ensures that instances do not inherit properties from source that are not meant for them, as shown in the following JavaScript example (TypeScript version here [again, other type errors remain]):

class A { // A: Model
    constructor(source){
        Object.keys(source)
        .forEach(key => {
            if(!this[key]){
                this[key] = source[key];
            }
        });
    }
}

class B extends A{ // B: User
    first_name;
}

let v = new B({first_name: "nof", shouldNotBeHere: "Hi there!"});
console.log(v);
// B { first_name: "nof", shouldNotBeHere: "Hi there!" }
//                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If you prefer to have A perform the blind copy, you can eliminate the compiler error (though not the problem itself) by disabling the flag.

Answer №3

Could you please specify the data type of the "n1" attribute in class B by including a type declaration:

n1!: string;

The exclamation mark after the variable name signifies that it is a non-nullable property, guaranteeing that "n1" will constantly hold a value.

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

Elevate the element from the choice API to the organization API using this.$parent

I recently developed a Vue 3 component called "Tab" using the option API. Here is the code: export default { name: "Tab", props: { name: {required: true}, iconClass: {required: true}, selected: {default: false} }, da ...

Editing an XML file on the browser and saving it in its original location on the local file system

Seeking assistance with editing an XML file directly from the browser on a local file system and saving it back to its original location. I have conducted extensive research using Google, but have not been able to find a suitable solution. Any help or gu ...

Tips for positioning a grid at the center of a MaterialUI layout

I am struggling to position 3 paper elements in the center of both the vertical and horizontal axes on the screen. Despite applying various CSS rules and properties, the height of the HTML element consistently shows as 76 pixels when inspected in the con ...

Webpack has issues with loading HTML files

I encountered a 404 not found error while attempting to load the HTML page using webpack. Here are my configurations: Webpack.config.js: const path = require('path'); module.exports= { devServer: { // contentBase static : { ...

Utilize ng-click in conjunction with an AngularJS directive

I'm encountering an issue while trying to create a directive and attaching ng-click with the template. Despite my efforts, when clicking on the template, it fails to log the statement from the scope.scrollElem function. The directive has been success ...

Incorporating external Angular 4 components within my custom components

For my project, I am attempting to incorporate jqWidgets Angular components: import { Component, ViewChild } from '@angular/core'; import 'jqwidgets-framework'; import { jqxTreeComponent } from "jqwidgets-framework/jqwidgets-ts/angula ...

What is the role of the "prepare" function in AWS CDK constructs?

TL;DR: What is the role and purpose of the prepare(): void method in AWS CDK's Construct class? When and how should it be utilized or avoided? The information provided about prepare() states: prepare() function is called after child constructs have ...

Is there a way to utilize multiple HTML templates with the same TypeScript file?

Is it possible to use different HTML templates for the same TypeScript file, based on an expression in the constructor? I am looking for something like this: <div class="container-fluid"> <app-teste1 *ngIf="teste == '1'> & ...

Having trouble implementing new controllers in AngularJS UI-Router for nested states?

I'm currently facing an issue with nested states in the ui-router. My task involves rendering a page that includes regions, countries, and individuals per country. In the index.html file, there are three regions represented as links: EMEA, APAC, and ...

How can I manually listen to Angular 2 events on a dependency injected instance?

Assume I am working with a component: @Component({selector: 'todo-cmp'}) class TodoCmp { @Input() model; @Output() complete = new EventEmitter(); // TypeScript supports initializing fields onCompletedButton() { this.complete.next(); / ...

Python responding to an Ajax request using jQuery

Currently, I am experimenting with integrating a pre-built inline editor from the following source: https://github.com/wbotelhos/inplace Regrettably, the available support documentation is lacking and my expertise in Javascript, jQuery, or Ajax is limited ...

Sliding divider across two div containers with full width

Seeking a JavaScript/jQuery function for a full page slider functionality. The HTML structure I'm working with is as follows: My objectives are twofold: Both slide1 and slide2 should occupy the full width of the page. Additionally, I am looking for ...

The TypeScript reflection system is unable to deduce the GraphQL type in this case. To resolve this issue, it is necessary to explicitly specify the type for the 'id' property of the 'Address'

import { ObjectType, ID, Int, Field } from 'type-graphql'; @ObjectType() export default class Address { @Field(type => ID) id: String; @Field() type: string; @Field() title: string; @Field() location: string; } More informa ...

Building Firestore subcollections with the latest WebSDK 9: A step-by-step guide

I'm looking to create a subcollection within my document using the webSDK 9, specifically the tree-shakable SDK. While I have come across some solutions, they all seem to be outdated and reliant on the old sdk. Just for context, I am working with Ne ...

What sets apart a space after the ampersand from no space in Material UI?

Could you clarify the difference between using a space after the ampersand compared to not having a space? For example: Why is there a space after the ampersand in & label.Mui-focused but no space in &.Mui-focused fieldset? const WhiteBorderTextF ...

What benefits does Observable provide compared to a standard Array?

In my experience with Angular, I have utilized Observables in the state layer to manage and distribute app data across different components. I believed that by using observables, the data would automatically update in the template whenever it changed, elim ...

Enhancing Angular functionality with the addition of values to an array in a separate component

I need help with adding a value to an array in the 2nd component when a button in the 1st component is clicked. I am working on Angular 4. How can I achieve this? @Component({ selector: 'app-sibling', template: ` {{message}} <butt ...

What is the reason behind utilizing the external 'this' within the inner function in Vue: $this=this?

Recently, I came across some code online that included a line $this=this. My initial interpretation was that this line assigns the 'this' of the outer function to a variable, allowing it to be used in the inner function as well. However, upon fur ...

The error message "ch.match is not a function" appeared in the Javascript code

Here are two functions that I need help with: //Function A var ltrToNato = function(ch) { var x = ch; var nato = ('{"A": "Alpha", "B": "Bravo", "C": "Charlie", "D": "Delta", "E": "Echo", "F": "Foxtrot", "G": "Golf", "H": "Hotel", "I": "India" ...

Can TypeScript support promise chaining in a functional way?

It appears that in the realm of JavaScript, one has the capability to execute: function extendPromise(promise) { return promise.then(new Promise(() => {})); } However, when incorporating types into the mix, such as function extendTypeScriptPromis ...