Reputation: 56
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
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