1

I am writing an app that has features that can be turned on and off via a config.json that looks something like this:

"appFeatures": {
    "header": {
      "create": true,
      "title": "Here Comes the Sun"
    },
    "imageStrip": {
      "create": false,
      "imageDirectory":  "../data/images",
      "imageDimensions": [288, 162]
    },
    "areaChart": {
      "create": true
    },
    "axes": {
      "create": true
    }
}

For each feature there is already a corresponding class of the same name that implements the feature. I'd like to use the name of the feature to create a new instance of the class. After fetching the config.json, I have code (within a Main.js class) that looks like:

this.features = Object.entries(this.config.appFeatures)
  .filter((entry) => {
    return entry[1].create === true;
  });

this.features.forEach((feature) => { this.createFeatureInstances(feature[0]); });

And then I try to create instances, a la this.header = new Header():

  createFeatureInstances(featureName) {
    const className = `${featureName.replace(featureName[0], featureName[0].toUpperCase())}`;

    this[`${featureName}`] = new Function(`
      return class ${className} {}
    `)();

This creates a new, empty Header class and, I suppose, it's instance. It is not the Header class that I have already written and want to create an instance for. How might I write the createFeatureInstances function so that I can create the instance of each class that corresponds to a feature?

EDIT Because new features may be added to this app in the future by others, I would like to minimize the times that I hard code which features are available to the app. With my current design, another developer can add another feature by writing a new feature class, importing that class into the Main.js class, and pop the config entries into the config .json without having to touch anything else in the code. For this reason, solutions like this one: Create an instance of a class in ES6 with a dynamic name? won't give me a solution because they rely on having a complete list of the classes that should already exist.

interwebjill
  • 920
  • 13
  • 38
  • 1
    Possible duplicate of [Create an instance of a class in ES6 with a dynamic name?](https://stackoverflow.com/questions/34655616/create-an-instance-of-a-class-in-es6-with-a-dynamic-name) – Robby Cornelissen Apr 17 '18 at 01:22
  • You need to have a name-class mapping somewhere. Factory function or somewhere else is your call. Your current solution lacks this, which is causing the problem: _"...creates a new, empty Header class and, I suppose, it's instance. It is not the Header class that I have already written and want to create an instance for"_ – maazadeeb Apr 17 '18 at 02:47
  • @MaazSyedAdeeb, thanks for letting me know that this is the case. I think this is the answer. If you'd like to post it, I'll accept it. I am importing the feature modules into `Main.js` via `import Feature1 from './features/Feature1.js`, for example. Based on your response, I now include the following in the constructor of `Main.js`: `this.Feature1 = Feature1`. Then I can use `this[`${featureName}`] = new this[`${className}`]();` and all works as desired. My next question is then https://stackoverflow.com/questions/49885861/javascript-can-i-access-the-object-that-contains-imported-modules – interwebjill Apr 17 '18 at 19:16

2 Answers2

1

You need to have a name-class mapping somewhere. Factory function or somewhere else is your call. Your current solution lacks this, which is causing the problem:

...creates a new, empty Header class and, I suppose, it's instance. It is not the Header class that I have already written and want to create an instance for

Some explanation with a simple example

// Imagine you've defined Test
class Test {
  callMe() {}
}

// And your consumer wants a className Test to be created
const className = "Test";

// This is a simple map of the name to class
const nameToClass = {
  "Test": Test
}

// This creates a new class called Test, not your defined class
const AttemptedDynamicTest = new Function(`
      return class ${className} {}
    `)();

// The next two logs just prove the previous comment
console.log(Test === AttemptedDynamicTest); // false
console.log(typeof AttemptedDynamicTest.prototype.callMe); // undefined

// What you really need is to maintain a map, and just use it to dynamically
// create a new instance every time
console.log(typeof nameToClass[className].prototype.callMe); // function
maazadeeb
  • 5,922
  • 2
  • 27
  • 40
0

You can use a string to initialize a cooresponding (valid and existing) class using the following snippet:

var dynamicClassInstance = new this[classNameString]();
Trent
  • 4,208
  • 5
  • 24
  • 46
  • 2
    When I use `this[`${featureName}`] = new this[className]();` I get the following error: `TypeError: this[className] is not a constructor` – interwebjill Apr 17 '18 at 02:00