rahbert
rahbert

Reputation: 56

Circular reference using dependency inversion issue

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;

Upvotes: 2

Views: 52

Answers (1)

Jelmer
Jelmer

Reputation: 353

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.

Upvotes: 1

Related Questions