Search code examples
node.jstypescriptcircular-dependency

Typescript/NodeJS - Circular Dependency while working with factories


I am quite new to Typescript and NodeJS, I am trying to use a factory to create objects on runtime.

As an example, the following object has property type Text, now getting that in runtime, I would like to create an instance of TextContainer:

{
type: "Text", 
name: "Title"
}

My factory looks like this:

import {BaseContainer} from "../Containers/BaseContainer";
import {TextContainer} from "../Containers/TextContainer";
/**
 * Used to dynamically create objects with different container classes
 * in runtime
 */
export class ContainerFactory {

    // Object containing all class names and types
    private dictionary: Object;

    /**
     * Initializes factory dictionary
     */
    constructor() {
        // Initialize dictionary with classes
        this.dictionary = {
            "default": BaseContainer,
            "Text": TextContainer,
            "TextContainer": TextContainer
        }
    }

    /**
     * Builds object depending on className
     * @param className
     * @param params
     */
    build(className: string, params: Object) {
        // Return new class of type className, if not found
        // return object with class of set default class
        return className in this.dictionary ? new this.dictionary[className](params) : new this.dictionary['default'];

    }
}

The problem comes in when in BaseContainer class (which is extended by TextContainer and will be extended by many more classes that will be present in this factory) I use the factory in a function, and here comes the circular dependency, because in BaseContainer I import ContainerFactory and in ContainerFactory BaseContainer is imported.

I need the factory in BaseContainer because I have a tree-like hierarchy and the containers have children, which are containers themselves.

I'd appreciate suggestions on how to work around this issue, or how to refractor my code in order for it to work reliably. I searched for similar issues but didn't find a workaround yet.

I receive the following error in the TextContainer class (extends BaseContainer):

extendStatics(d, b);
        ^

TypeError: Object prototype may only be an Object or null: undefined

Solution

  • A better solution for this task would be to use decorators for the purpose of mapping the type names to respective constructor functions. For example:

    Decorator.ts:

    export function Container(className: string)
    {
        return (target: any) =>
        {
            Meta.classNameToCtor[!!className ? className : target.name] = target;
        };
    }
    

    Meta.ts:

    export class Meta
    {
        public static classNameToCtor: {[key: string]: any} = {};
    }
    

    Now all you need to do is decorate each of your container classes like this:

    @Container("default")
    export class BaseContainer {...}
    

    And in your factory access constructors via Meta:

    build(className: string, params: Object) 
    {
        return className in Meta.classNameToCtor ? new Meta.classNameToCtor[className](params) : new Meta.classNameToCtor['default'];
    }
    

    This approach kills import dependencies altogether and is much more scalable/elegant to use.