2

I have a circular reference issue using this pattern approach. TypeError: Class extends value undefined is not a constructor or null .

The strange thing is, if I move the field.type.ts in src/constants.ts, it doesn't throw an error and it works as expected, but crashes on the Unit Tests. If it leave the fied.type.ts contents in it's own file, it crashes.

Maybe I am not using/understanding this dependency inversion pattern the right way. I could probably fixed by passing the FieldTypeToClassMapping as a parameter in Field.create(options: FieldOptions, fieldTypeMapping: FieldTypeToClassMapping), but I want to understand why this is happening.

import { StringField } from './string.field.model';
import { IntegerField } from './integer.field.model';
...

export const FieldTypeToClassMapping = {
  //Constructor of eg. StringField class so I can use `new FieldTypeToClassMapping[options.type](options)`;
  [FieldTypeEnum.STRING]: StringField,
  [FieldTypeEnum.INTEGER]: IntegerField,
};
//field/field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface';
import { FieldTypeToClassMapping } from 'src/model/template/field.type.to.mapping.ts'

export abstract class Field {
  value: any;
  type: string;

  errors: string[] = [];

  public constructor(options: FieldOptions) {
    this.value = options.value;
    this.type = options.type;
  }

  public static create(options: FieldOptions): any {
    try {
      return new FieldTypeToClassMapping[options.type](options);
    } catch (e) {
      throw new Error(`Invalid field type: ${options.type}`);
    }
  }
}
//field/integer.field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface';
import { Field } from './field.model';

export class IntegerField extends Field {
  constructor(options: FieldOptions) {
    super(options);
  }
  protected validateValueDataType() {
    this.validateDataType(this.value, "value");
  }

  protected validateDefaultDataType() {
    this.validateDataType(this.defaultValue, "defaultValue");
  }
}
//field/service.ts
payload const postFields = [
  {
    type: "string", //FieldTypeEnum.STRING,
    value: 'a name'
  },
];

const postFields = [
  {
    type: "string",
    value: "John",
  },
  {
    type: "integer",
    value: 32,
  },
];


const fieldsArray = [];
postFields.forEach((item) => {
    const field: Field = Field.create(item);
    fieldsArray.addField(field);
  });

return fieldsArray;
4
  • 1
    Have each child class register itself in the FieldTypeToClassMapping instead of importing all the child classes in the same module that declares the parent class. Commented Jan 18, 2023 at 10:09
  • Yes, this is a circular module dependency problem, so please edit your question to show the module import statements in your code Commented Jan 18, 2023 at 10:10
  • Which module system are you using? Do you compile TypeScript to ES6 modules or to CommonJS? Commented Jan 18, 2023 at 10:12
  • @Bergi Sorry for the late reply. I have added the imports for context. Also, CommonJS. In the meanwhile I fixed it by passing the FieldTypeToClassMapping as a parameter in the create() method. Commented Jan 26, 2023 at 9:17

1 Answer 1

1

The create(options: FieldOptions) function is defined inside the class Field, but then it tries to instantiate an instance of a class that extends Field.

I think that is where the problem arises. I don't know the entire contents of your files, but I imagine that at the top of any field.type.ts file you import Field. However since Field can instantiate any concrete implementation of itself it would need to know about them so you would need to import everything that extends Field inside Field.

I don't know/understand the dependency inversion pattern well enough to relate it to your question. But given the provided information, perhaps a Factory Pattern is what you need?

You could move the the function create(options: FieldOptions) to a FieldFactory class. Your create function is practically a factory function already.

Sign up to request clarification or add additional context in comments.

1 Comment

Yes, my thought where the same. The mind bending thing was, it didn't crash when I've put the FieldTypeToClassMapping in an existing file constants.ts, which was only temporary. After moving it to a different file, crashed. Moved it back, worked. I was thinking I missed something and was curious of what. Did the usual, rebuild, deleted dist, etc. Same behaviour. Really strange. I guess I'll try the factory pattern on the next refactoring.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.