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

Understanding the fundamentals of TypeScript annotation and node package management

As a newcomer to Typescript, I have grasped the basics but find myself becoming a bit bewildered when it comes to best practices for handling node packages, annotations, and defining types within those packages in my projects. Do I really need to annotate ...

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> ...

Is there a workaround for the issue of the NodeJS Web Cryptography API require() being undefined in an unsecure origin (Heroku App)?

My goal is to implement the experimental Web cryptography API (SubtleCrypto) on my Node.js server hosted on Herokuapp. The aim is to encrypt data from a fetch request sent from gitpages to herokuapp, concealing sensitive information from the browser consol ...

Package.json file is not included in Typescript

Each time I execute tsc, it converts the files to JS format successfully, except for package.json. I want this file included in my output directory. Currently, my tsconfig.json looks like this: { "exclude": ["node_modules"], "compilerOptions": { " ...

Adding a custom class to the body element for specific routes in Next.js can be achieved by utilizing the features of

I am looking to apply my custom class to certain pages, with the exception of specific routes. For example, all pages should have the class fixed-header, except for the following routes: /cart/step-1 /login This class should be added or removed from the ...

Although AJAX $.post functions properly in the View, it seems to encounter issues when relocated to a separate .js file. Interestingly, all other JQuery functions work

I have recently delved into MVC, JQuery, and AJAX, and encountered a perplexing issue. After completing the initial development of a practice website, I dedicated time to enhance the interactivity using JQuery. Everything was functioning smoothly until I ...

Encountered a problem while trying to inherit from the BrowserWindow class in

I utilized the https://github.com/SimulatedGREG/electron-vue template to craft a vue electron blueprint. Alongside the primary process index.js, I fashioned a file named MainWindow.js which holds the subsequent code: import { BrowserWindow } from 'el ...

Unable to access res.name due to subscription in Angular

Right now, I am following a tutorial on YouTube that covers authentication with Angular. However, I have hit a roadblock: The code provided in the tutorial is not working for me because of the use of subscribe(), which is resulting in the following messag ...

AngularJS Data Binding Issue - Watch cycle fails to trigger

View the JSFiddle example here: https://jsfiddle.net/pmgq00fm/1/ I am trying to achieve real-time updating of my NVD3 chart by utilizing the setInterval() function on line 39, which updates the data bound to the directive. Here is a brief overview of the ...

Changing the text color of Material UI Accordion Summary upon expansion

How can I update the color of an Accordion summary text in Material UI when it is expanded? <Accordion expanded={expanded === 'panel1'} onChange={handleChange('panel1')} className={classes.root}> <AccordionSummary ...

Angular 2 Directive for Ensuring Required Conditions

Is there a way to make form fields required or not based on the value of other fields? The standard RequiredValidator directive doesn't seem to support this, so I've created my own directive: @Directive({ selector: '[myRequired][ngControl ...

Getting the parent from a child in Typescript: Best Practices

Querying: In TypeScript, is it possible to retrieve a parent instance from a child instance? I am aware that casting a child into its parent is a method, however, the child's additional properties still exist in the parent, albeit concealed. Check o ...

Utilizing Subdirectories in a Command Manager

My goal is to organize my commands into sub folders, but for some reason my bot is not recognizing the commands inside those folders. Strangely, no error message is being displayed. const fs = require('node:fs'); const Discord = require('dis ...

Alert: Parser error in JSONP!

$.ajax({ type: "GET", dataType: "jsonp", jsonpCallback: "jsoncallback", //async: true , data: { // some other data here }, url: "http://mywebsite.com/getRequest.php", success: function(response ...

Locate a specific item in an array using AngularJs based on a key and present its value on the View

Imagine you have an array of objects: $scope.objArr = [{key:1,value:'value1'},{key:2,value:'value2'},{key:3,value:'value3'}]; And a variable that corresponds to key. For instance: $scope.a = 3; In the Controller, you want ...

Toggle the visibility of a dropdown menu based on the checkbox being checked or unchecked

One challenge I am facing involves displaying and hiding DropDown/Select fields based on the state of a Checkbox. When the checkbox is checked, the Dropdown should be visible, and when unchecked, it should hide. Below is the code snippet for this component ...

Utilize Next.js to send an image to an email by leveraging the renderToString function on the API routes

I need help with sending styled emails that contain images. Currently, I am utilizing the renderToString method to pass props into my component. So far, everything is functioning correctly in the API routes. mport client from "@/lib/prisma"; im ...

Exploring the history and present state of Vue lifecycle hooks

Is there a way to access previous and current data in the updated lifecycle hook in Vue, similar to React? I want to be able to scroll a list of elements to the very bottom, but for this I need: The already rendered updated DOM (to calculate the scroll) ...

A Guide to Catching Targeted React Notifications in Sentry using Next.js

In my Next.js application, I have implemented Sentry for error tracking. While I have successfully set up Sentry to capture errors within my try/catch blocks, I am currently struggling with capturing specific errors and warnings at a global level in my sen ...

Using jQuery to fetch and display content only when the user hovers over

Looking to optimize page loading speed by minimizing the loading time of social icons such as Facebook Like and Twitter follow buttons. Considering displaying a static image of the like buttons initially, with the actual buttons appearing on mouse hover. ...