I am fairly new to typescript and have ran into a problem concerning circular dependencies and child class instantiation from parent classes. I have a parent class that will ideally contain a static method which fetches various data and instantiates a certain child class depending on the retrieved data. I will have many child classes, thus I'd like to keep the children in separate files (thus the circular dependency problem). Here is some code for an example:
parent.ts
abstract class Parent {
_data1: string;
_data2: number;
constructor (
data1: string,
data2: number,
){
this._data1 = data1;
this._data2 = data2;
};
static async createChildren(dataQueryURL: string): Promise<Array<Parent>> {
const dataQuery: Array<Array<any>> = await getQueryData(dataQueryURL);
// Suppose response looks like: [["foo", 3, 1], ["bar", 2, 2, "Hello world!"], ["baz", 5, 1]]
const children: Array<Parent> = new Array<Parent>();
dataQuery.forEach(data => {
if(data[2] === 1){
let child = new Child1(
data[0],
data[1],
data[2],
);
children.push(child);
} else if (data[2] === 2){
let child = new Child2(
data[0],
data[1],
data[2],
data[3],
);
children.push(child);
};
});
return children;
};
abstract doSomething(): number;
};
child1.ts
class Child1 extends Parent {
_data3: number;
constructor(
data1: string,
data2: number,
data3: number,
){
super(data1, data2);
this._data3 = data3;
};
doSomething(): number {
return this._data2 * this._data3;
};
};
child2.ts
class Child2 extends Parent {
_data3: number;
_data4: string;
constructor(
data1: string,
data2: number,
data3: number,
data4: string,
){
super(data1, data2);
this._data3 = data3;
this._data4 = data4
};
doSomething(): number {
return this._data2 / this._data3;
};
};
As you can clearly see, there must be circular dependencies between parent.ts <--> child1.ts and parent.ts <--> child2.ts. Typescript throws an error at compilation time due to this.
Furthermore, when I call the static method createChild, I return an array of classes that all inherit from parent but I don't know for sure which child classes will be in the array. As a result I promise an array of parents. I have a hunch this is an incorrect way of thinking about this problem. Reason being: if I want to go through my array of children and get _data3, typescript will not expect _data3 to be contained within the classes as the parent does not contain _data3.
I can solve the circular dependencies by getting rid of the static method and creating another ts file with a function for createChildren. I can solve the _data3 typing problem by promising an array of any. However both of these seem like messy solutions. I am hoping for some insight on how to deal with both the circular dependencies and promises in a more elegant fashion. Thanks in advance!
You can break the compile-time import dependency by making a dynamic import (and remove the top-level import from parent.ts
module)
Something like :
static async createChildren(dataQueryURL: string): Promise<Array<Parent>> {
const dataQuery: Array<Array<any>> = await getQueryData(dataQueryURL);
// Suppose response looks like: [["foo", 3, 1], ["bar", 2, 2, "Hello world!"], ["baz", 5, 1]]
const children = await Promise.all(dataQuery.map(async data => {
if(data[2] === 1){
return new (await import('./child1')).Child1(
data[0],
data[1],
data[2],
);
} else if (data[2] === 2){
return new (await import('./child2')).Child2(
data[0],
data[1],
data[2],
data[3],
);
} else {
throw new Error(`Unsupported type: ${data[2]}`);
};
}));
return children;
}
Regarding your second question, I'm not sure to fully understand it :
Why not bring _data3
to Parent
class if this field exists (with same type) in both Child1
and Child2
? or make an abstract getter for this field at the Parent level ?
In that case, you would be able to retrieve it in a generic fashion.
In your example, it seems like you can have responses mixing both Child1
and Child2
shaped data structures : it seems legit to me to then have a generic Array<Parent>
result type.
We may consider having a more specialized type if you would tell me that responses will always contain the same type of Child.
Regards,