The class field remains unset

I'm currently developing my own custom CQRS library and I've encountered an issue where a specific field is not being set correctly. The problem appears to be within classes that extend my AggregateRoot abstract class. Take a look at the following TypeScript code snippet:

import Entity from "./entity";
import Event from "./event";

export enum AggregateRootConstructionTypes {
    Create,
    Load
}

type CreateConstructorParams = {
    type: AggregateRootConstructionTypes.Create,
    createParams?: any;
}

type LoadConstructorParams = {
    type: AggregateRootConstructionTypes.Load,
    history: Event[]
}

type AggregateRootConstructorParams = CreateConstructorParams | LoadConstructorParams;

abstract class AggregateRoot extends Entity {
    private _events: Event[] = [];
    private _version: number = -1;

    get domainEvents(): Event[] {
        return this._events;
    }

    get version(): number {
        return this._version;
    }

    addDomainEvent(event: Event): void {
        this._events.push(event);
    }

    clearEvents(): void {
        this._events.splice(0, this._events.length);
    }

    protected apply(event: Event): void {
        this._events.push(event);
        this._version++;
        this.when(event);
    }

    protected abstract when(event: Event): void;
    protected abstract create(input: any): void;

    protected loadFromHistory(history: Event[]): void {
        history.forEach((event) => {
            this.when(event);
            this._version++;
        });
    }

    constructor(params: AggregateRootConstructorParams) {
        super();
        if (params.type === AggregateRootConstructionTypes.Create) {
            this.create(params.createParams);
        } else {
            this.loadFromHistory(params.history);
        }
    }
}

export default AggregateRoot;

The discrepancy can be seen in the following test scenario:

import AggregateRoot, {AggregateRootConstructionTypes} from "./aggregate-root";
import Event from "./event";

describe('AggregateRoot', () => {
   class CreatedEvent extends Event {}

    class ExtendedAggregateRoot extends AggregateRoot {
       create() {
           const createdEvent = new CreatedEvent();
           this.apply(createdEvent);
       }

       someState: number = 0;

       private handleCreatedEvent() {
           this.someState = 1;
       }

       protected when(event: Event) {
           if (event instanceof CreatedEvent) {
               this.handleCreatedEvent();
           }
       }
   }

    describe('when applying an event', () => {
       it('should update the version, register an event, and trigger the handler in the when() function', () => {
           const EAR = new ExtendedAggregateRoot({ type: AggregateRootConstructionTypes.Create });
           expect(EAR.version).toEqual(0);
           expect(EAR.domainEvents).toHaveLength(1);
           expect(EAR.someState).toEqual(1); // ----------> this line fails
       });
    });
});

In the final expect statement, the test fails. I am seeking assistance in understanding why this occurs. Despite logging data throughout the process, it seems that 'someState' is unexpectedly set to 1 but upon evaluation, it remains at 0. I suspect there may be an issue with the execution context in my coding structure, however, isolating the exact cause has proven challenging. Any insights or guidance would be greatly appreciated.

Answer №1

To resolve this issue, it is essential to review and adjust your TypeScript compiler settings.

According to the documentation on type-only field declarations in TypeScript:

If target >= ES2022 or useDefineForClassFields is set to true, class fields get initialized after the parent class constructor finishes execution, overriding any value previously set by the parent class.

In a typical scenario, the sequence of actions unfolds as follows:

  1. new ExtendedAggregateRoot({ type: AggregateRootConstructionTypes.Create })
    gets called
  2. The AggregateRoot constructor triggers this.create(params.createParams)
  3. create within ExtendedAggregateRoot calls this.apply(createdEvent)
  4. apply inside AggregateRoot invokes this.when(event)
  5. when within ExtendedAggregateRoot executes this.handleCreatedEvent()
  6. handleCreatedEvent in ExtendedAggregateRoot sets this.someState = 1
  7. The constructor completes its operations
  8. ExtendedAggregateRoot then "initializes" someState to 0

Various solutions exist for rectifying this particular issue. One suggested approach involves restructuring the codebase by segregating entity state data from event application mechanisms. Instead of storing the state directly within the ExtendedAggregateRoot class, consider treating it as a distinct data structure passed into the parent constructor. Additionally, modify the when method to accept the current state and return a new state resulting from applying the event to the existing state.

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 is the best way to set a specific maximum size for the dropdown selector in Material UI's TextField component?

Working on a Sign up Page involves prompting users to select their location from a dropdown list of different countries. However, the issue arises when the dropdown list covers the entire screen, making it difficult for users to navigate. Is there a way to ...

Ways to establish the relationship between two fields within an object

These are the definitions for two basic types: type AudioData = { rate: number; codec: string; duration: number; }; type VideoData = { width: number; height: number; codec: string; duration: number; }; Next, I need to create a MediaInfo typ ...

Exploring the linewidth feature in Three.js when working with LineSegmentsGeometry and LineSegments2

My goal is to create lines wider than one pixel using the LineMaterial and LineSegments2 classes from the threejs examples library. To achieve this, I've been following the guidelines provided in the response to a similar inquiry found here: Three.js ...

Timing of Vue mounting and initialization phases

I am currently working on a component where I pass the reference to an internal element to the parent using defineExpose. In this example, I simply log the element, but in my actual project, I intend to manipulate it. SFC Playground // Comp.vue <scrip ...

Difficulty encountered when attempting to invoke a public function that makes use of a private function in TypeScript

I'm relatively new to TypeScript and I've been trying to replicate a pattern I used in JavaScript where I would expose functions through a single object within a module (like "services"). Despite my efforts, I'm facing some issues when attem ...

In what ways can I leverage the functionalities of an AngularJS directive to delay the display of content until a user clicks on it?

Our rental application utilizes an API call to populate an array, triggering an ngRepeat and generating a list of divs displaying basic rental property information. Upon clicking a specific property, another API call is made to populate an interior ngRepe ...

Encountering a JSLint error while attempting to import the 'aws-amplify' package in my main file

Recently, I installed the latest version of aws-amplify using npm. After running npm init with 'entryPoint.js' as the entrypoint file, I encountered an issue when pasting the following code at the top of entryPoint.js: import Amplify, {Auth} from ...

What is the solution for resolving the error message, "The type 'string | boolean' cannot be assigned to type 'never'. The type 'string' cannot be assigned to type 'never' "?

interface DataStructure { name: string; url: string; date: string; popular: boolean; } const itemData: DataStructure = { name: "Item 1", url: "item-1", date: "2012", popular: true, }; function getItemByURL(keys: Array<k ...

Struggling with parsing JSON in TypeScript/React because of type issues

Upon receiving an API response, I would like to parse the data since it is in the form of an array of objects. Here's an example of how the JSON looks: { "totalSize": "56", "sortedKeys": [ { & ...

Using ngRepeat to Minimize TH-Tags in AngularJS

Here is the current section of TH-Tags in the view: ... <th> <a href="" ng-click="sortReverse = !sortReverse; order('fname',sortReverse)"> Firstname <span ng-show="sortType=='fname' && ...

Utilizing Node.js: How to access another function within the same controller?

I am facing an issue with accessing one function from another in my code. How can I achieve this? class firstController { firstFunction(req, res) { var stamp = request.query("Select 'ALB'+left(newid(),5)+right(newid(),5)+ left(n ...

When running `npm test`, Mocha TS tests encounter failure, but the issue does not arise when executing them

When running tests in my Typescript nodejs project, I use the following command: mocha --compilers ts:ts-node/register,tsx:ts-node/register The tests run successfully with this command. However, when I try to run them using npm test, I encounter the foll ...

Zebra Calendar Alignment Problem

Currently working on an asp.net app and utilizing the Jquery Zebra DatePicker. Encountering an issue where when opening the form in a Jquery dialog, the calendar appears at the bottom of the page instead of within the dialog. A similar problem was discus ...

UV mapping with Plane BufferGeometry in Three.js

I'm facing some challenges creating a buffergeometry plane, specifically with the uv coordinates. Despite following advice from Correct UV mapping Three.js, I can't seem to achieve the desired result. Below is the snippet of code for the uv coor ...

Sorting alphabetically, either by JAVA, JavaScript, or Idoc script

Currently, I have a task at hand that requires sorting items within categories alphabetically, with the exception of Examples. Special characters and numbers should be prioritized over letters in the sorting order. I've encountered an issue where mos ...

Using ThreeJS to Load a Texture from an ArrayBuffer in JavaScript

If I have a JavaScript ArrayBuffer containing binary image data along with the image extension (such as jpg, png, etc), I want to create a ThreeJS Texture without the need for an HTTP request or file load as I already have the binary information. For exam ...

Encoding URLs with PHP and JavaScript for enhanced security

I am currently working with a HTML text that was encoded in PHP using the urlencode function. Now, I need to decode this text in JavaScript. However, when I use the unescape function in JavaScript, all special characters are reverting back except for spa ...

Interactive webpages with dynamic HTML content, similar to the design of popular platforms such

Explore the source code of Dropbox's homepage or any Soundcloud page. They utilize various scripts and minimal pure HTML content (article, main, p, div). This method of generating pages is referred to as dynamic content/HTML. The main function seems ...

ESLint does not recognize the components used in Element UI

I've been working with Vue.js and Element UI components. However, when I try to use elements like Input or Col, ESLint throws an error with the message invalid-end-tag. I have already added eslint-plugin-vue to my setup, so why isn't ESLint reco ...

Debating the use of cameras in Three.js

Creating a camera in code typically looks like this: var camera = new THREE.PerspectiveCamera( FOV, ASPECT_RATIO, NEAR_PLANE, FAR_PLANE ); But what exactly do these values represent? ...