Circular dependency in Typescript/Javascript: Attempting to extend a class with an undefined value will result in an error,

Query

Greetings, encountering an issue with the code snippet below:

TypeError: Super constructor null of SecondChild is not a constructor
    at new SecondChild (<anonymous>:8:19)
    at <anonymous>:49:13
    at dn (<anonymous>:16:5449)

I am currently delving into the root cause of this error and exploring how to effectively implement the factory pattern in typescript/javascript. There are certain behaviors that appear perplexing:

  1. If I exclude the second-child segment from the code, everything runs smoothly except when swapping the order of export statements in the index.ts file between child and parent.
  2. Merging the files child.ts and second-child.ts into a single file containing both classes eliminates the exception.
  • Is the error related to circular dependencies? If so, what exactly triggers issues with circular dependencies in typescript/javascript?
  • Could someone provide insight into the behavior observed in the code example?

To avoid the error, one could deviate from utilizing the type "this" while employing the factory Method for implementing the parent class, although it offers convenience. The objective behind the abstracted method duplicate is to yield a subclass instance, a repetitive implementation in every subclass may result in higher complexity within my real-world project.

Structure of Project:

src/
  child.ts
  factory.ts
  index.ts
  parent.ts
  main.ts
  second-child.ts

Content Overview

main.ts:

// Throws error
import { Child, Parent, SecondChild  } from './index'


console.log(new Child().duplicate())
console.log(new SecondChild().duplicate())
console.log(new Parent().duplicate())

// Works as intended:
// import { Child, Parent} from './index'


// console.log(new Child().duplicate())
// console.log(new Parent().duplicate())

parent.ts

import { factory } from './factory'

export class Parent {
  isChild = 0;
  duplicate(): this {
    return factory(this.isChild) as unknown as this;
  }
}

child.ts

import {Parent} from './parent'

export class Child extends Parent{
  override isChild = 1;
}

second-child.ts

import {Parent} from './parent'

export class SecondChild extends Parent{
  override isChild = 2;
}

factory.ts

import { Child } from './child'
import { SecondChild } from './second-child'
import { Parent } from './parent'

export function factory(child: number):Child;
export function factory(...args: unknown[]):Parent {
  switch (args[0]) {
    case 1: {
      return new Child()
    }
    case 2:  {
      return new SecondChild()
    }
    default: {
      return new Parent();
    }
  }
}

index.ts

export * from './child'
export * from './second-child'
export * from './parent'
export * from './factory'

Project Insight

  • Starting point: main.ts
  • index.ts orchestrates content exports from child.ts, factory.ts, parent.ts, second-child.ts

Sample Playground

Answer №1

Circular dependencies among modules can be quite intricate. The sequence of evaluation is influenced by the order of the import statements and the entry point, making it very delicate. JavaScript executes the code in a module after fulfilling its dependencies, utilizing a DFS graph traversal method. However, it must disregard dependencies that were previously visited and are still pending evaluation of their own dependencies.

In this scenario,

  • main.ts imports index.ts
    • index.ts imports child.ts
      • child.ts imports parent.ts
        • parent.ts imports factory.ts
          • factory.ts imports child.ts, but it does not await its evaluation (as we are already attempting to evaluate it)
          • factory.ts imports second-child.ts
            • second-child.ts imports parent.ts, but it does not wait for its evaluation (we are already trying to evaluate it)
            • second-child.ts has no more imports remaining, so its code gets evaluated. The Parent variable is set up, but remains uninitialized, leading to an exception being thrown.

To resolve this issue, you can modify the imports in factory.ts to

import { Child, SecondChild, Parent } from './index';

With this change, the traversal proceeds as follows:

  • main.ts imports index.ts
    • index.ts imports child.ts
      • child.ts imports parent.ts
        • parent.ts imports factory.ts
          • factory.ts imports index.ts, but does not wait for completion (as we are trying to evaluate it first)
          • factory.ts has no more imports left, so its code is executed. It defines the factory function which, since it's not immediately called, avoids accessing the uninitialized imported variables.
        • parent.ts has no more imports left, so its code is executed, initializing Parent.
      • child.ts has no more imports left, so its code is executed, initializing Child (given that Parent has been initialized).
    • index.ts imports second-child.ts
      • second-child.ts imports parent.ts, which has already been evaluated
      • second-child.ts has no more imports left, so its code is executed, initializing SecondChild
    • index.ts imports parent.ts, which has already been evaluated
    • index.ts imports factory.ts, which has already been evaluated
    • index.ts has no more imports left, so its code is executed (which is empty and performs no actions)
  • main.ts has no more imports left, so its code is executed, creating instances and calling their methods, including the factory() function where the imports are now initialized

A tool like dpdm can assist in comprehending even complex dependency graphs. It also encourages avoiding circular dependencies, as they tend to cause more issues than they solve if avoided when possible.

In this case, I suggest implementing the duplicate feature without using factory, opting instead for conventional cloning methods (refer to here or there), or modifying the implementation of factory by employing a class registry to which classes can register themselves, rather than importing them all into factory.ts.

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

Utilizing multiple materials with a single mesh in three.js: A comprehensive guide

I am facing a major issue with three.js: My goal is to create a simple cube with different colors on each face. I attempted to achieve this using the following code snippet: // set the scene size var WIDTH = jQuery('#showcase').width() - 20 ...

Utilizing the components within the range set by paper.setStart() and paper.setFinish() in Raphaels

My question has two parts - the first and second part. Let's consider an example code that I am working on. I am creating a map of my country with regions, and I want to perform actions on the entire map such as scaling or translating (as seen in the ...

Adjusting the size of div content without changing its dimensions

I'm in search of a solution for resizing the contents within a fixed width and height div. Currently, my div looks like this: <div id="editor_preview" style="width:360px !important; color:gray; ...

Transform the Nodejs server into a reusable Node module

Can a nodejs API server be transformed into a node module for use in other projects with minimal code modifications? Additional Information: The node js server contains various APIs (get, post, put). If I integrate this server as a node module within anot ...

I'm having trouble executing the straightforward code I discovered on GitHub

https://github.com/Valish/sherdog-api Upon downloading the sherdog-api, I embarked on installing node.js as a prerequisite for using this new tool, despite having no prior experience with such software. Following that, I opened up the command prompt and n ...

Why isn't changing the property of a Sequelize instance having any effect?

While I've successfully used the standard instance syntax in the past, I'm facing a challenge with updating an instance retrieved from the database in this specific section of my code. ... const userInstance = await db.models.Users.findOne({wher ...

Attach an event listener to the HTML content

Being new to Javascript, I am attempting to add an event listener for each button on every card. However, the code seems to only apply the event 'click' to the last card (button). Is there a way to make this work with the innerHTML of the card? l ...

Guide on showing a dropdown menu depending on the data in the current array index within a table

I am working with an object array that I want to display in a table. My goal is to have a dropdown select if the 'validvalues' field is not empty. How can I achieve this so that each row in the table has different options from the array? When &ap ...

What is the best way to store a small number of files in the state

I have recently implemented Drag and Drop functionality, and now I am facing an issue where I need to save a few files in state. const [uploadedFiles, setUploadedFiles] = useState<any[]>([]); const onDropHandler = async (e: React.DragEvent<HTMLDi ...

Passing information from a PHP array using a JavaScript forEach loop to the URL of an AJAX request

I have a MySQL query in PHP that returns an array of order IDs, and I want to iterate through these IDs using JavaScript. The goal is to use the IDs in an AJAX call to update the dates of these orders in the database. MySQL query: <?php // SQL ...

The Typescript error occurs when trying to assign a 'string' type to a 'SetStateAction<null>'

For my project, I am delving into creating a global context using TypeScript. As a newcomer to TypeScript, I found a helpful guide in this blog post (). Despite following the outlined steps, I keep encountering an error message saying "Type 'string&ap ...

What are the reasons behind the significant difference in speed between using Node's Object.create(foo) and new Foo()?

For my Sudoku solver in JavaScript, I decided to take a purely functional approach by using immutable 9x9 puzzle arrays. Every time a new number is inserted, a new array is created. Implementation 1: New SudokuPuzzle In the initial version, I utilized th ...

Send a POST form from my website to another website and retrieve the data from the remote server

My website, example.com, includes a web HTML form that leads to another site where the results are currently displayed. I want to retrieve these results from the other website using PHP or some other method and showcase them on my own site. However, the fo ...

Saving compiled babel files to the corresponding directory level

Is there a way to output compiled babel files in the same directory as the source files? Currently, my script generates the compiled files in a separate folder while maintaining the folder structure. This is achieved through the following code snippet in m ...

Ensure that all items retrieved from the mongoDB query have been fully processed before proceeding further

In the midst of a challenging project that involves processing numerous mongoDB queries to display data, I encountered an issue where not all data was showing immediately upon page load when dealing with large datasets. To temporarily resolve this, I imple ...

Function necessary for successful data binding

The following code is causing an issue for me: <!DOCTYPE html> <html ng-app="m1"> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script> </head> <body> ...

Accessing data in form rules with Vuetify: tips and tricks

Is it possible to access a data element within a rule? Click here to see my code in action I'm attempting to change the value of a data element on a text field rule within a Vuetify form. While the rule itself is functioning properly, I'm enco ...

The use of a map with Next/image seems to be preventing the src image from loading properly

Utilizing next/image for loading images in my application has been successful, except when it comes to a carousel featuring multiple images. Whenever I attempt this, I encounter the following error: Error: Image is missing required "src" property. Make su ...

In Angular JS pagination, the previous filter value is stored in $LocalStorage for future reference

One view displays all order records in a tabular format with 10 records per page. A filter is set to show only paid orders, which pops up filtered data when selected. An issue arises when closing the pop-up window and navigating to the next page of the t ...

tips and tricks for adding content to json with the help of jquery/javascript

Is it possible to include text in a JSON array using jQuery or Javascript? Assuming that there is data in the following format: A list of numerical codes stored in a text file as 123, 456, 789 I am looking to convert them into a JSON array using JavaScr ...