68

I want to be able to instantiate a particular ES6 class by passing a string variable to a function. Depending on the value of the variable, a different class will be created.

Example - I have 2 classes, ClassOne, ClassTwo. I want to be able to pass a variable to a function and get a new class back. The name of the class will be related to the variable - eg. passing 'Two' will create ClassTwo.

I don't want to just use a switch clause like this:

function createRelevantClass( desiredSubclassName )
{
  let args = [],
      newClass;

  switch( desiredSubclassName )
  {
    case 'One' :
      newClass = new ClassOne(args);
      break;
    case 'Two' :
      newClass = new ClassTwo(args);
      break;
  }

  return newClass;
}

Instead, I want to somehow be able to create the constructor call using the variable name. Is that possible?

function createRelevantClass( desiredSubclassName )
{
  // desiredSubclassName would be string 'One' or 'Two'

  // how to use the 'new' operator or Reflect here to create the class based on the variable passed in
  let newClass = ( *magic code to build constructor dynamically* );

  return newClass;
}
sdgluck
  • 24,894
  • 8
  • 75
  • 90
codewithfeeling
  • 6,236
  • 6
  • 41
  • 53
  • I could not add an answer here because this is closed, but anyone looking for a modern (2022+) solution look at this answer: https://stackoverflow.com/a/71624436/3193156 – Blizzardengle Mar 26 '22 at 00:35

2 Answers2

105

There are a few ways you can accomplish this...

1. Proxy Class

Following from @thefourtheye's example of maintaining a mapping of name to class, you could have a class whose job is to take the name of the desired class and proxy its instantiation:

[ See it working ]

Define your classes

// ClassOne.js
export class ClassOne {
    constructor () {
        console.log("Hi from ClassOne");
    }
}

// ClassTwo.js
export class ClassTwo {
    constructor (msg) {
        console.log(`${msg} from ClassTwo`);
    }
}

Define the proxy class (e.g. DynamicClass)

import ClassOne from './ClassOne';
import ClassTwo from './ClassTwo';

// Use ES6 Object Literal Property Value Shorthand to maintain a map
// where the keys share the same names as the classes themselves
const classes = {
    ClassOne,
    ClassTwo
};

class DynamicClass {
    constructor (className, opts) {
        return new classes[className](opts);
    }
}

export default DynamicClass;

Example usage

import DynamicClass from './DynamicClass';

new DynamicClass('ClassOne'); //=> "Hi from ClassOne"
new DynamicClass('ClassTwo', 'Bye'); //=> "Bye from ClassTwo"

2. Factory Function

Use a function that performs a lookup against an object of class name -> class mappings and returns reference to the class, which we can then instantiate as usual.

Define the factory function

import ClassOne from './ClassOne';
import ClassTwo from './ClassTwo';

const classes = { ClassOne, ClassTwo };

export default function dynamicClass (name) {
  return classes[name];
}

Example usage

import dynamicClass from './dynamicClass'

const ClassOne = dynamicClass('ClassOne') // Get the ClassOne class

new ClassOne(args) // Create an instance of ClassOne
sdgluck
  • 24,894
  • 8
  • 75
  • 90
  • 1
    I like this one because I don't have to explicitly build a map of variables to classes - just having a constant with the group of classes to construct is enough. @thefourtheye's example is neat but I think this is closer to what I wanted to do. – codewithfeeling Jan 07 '16 at 13:56
  • 11
    It actually doesn't have to be a class. It can be a simple factory function. A class is kinda overkill IMHO. – thefourtheye Jan 08 '16 at 11:12
  • @thefourtheye Yep, certainly not the only way. :-) – sdgluck Jan 08 '16 at 11:20
  • 1
    In the end I have combined the two - a simple factory function which uses the non-mapped constant of class references. Thanks both for your great help. – codewithfeeling Jan 08 '16 at 11:40
  • 3
    I do not agree with this approach. The return value shouldn't be a class, however should be an object of the requested class from the Factory. Please review the following link: https://en.wikipedia.org/wiki/Factory_method_pattern – Ehsan Aug 03 '17 at 13:43
  • 2
    This is not very handy if you have a library with many classes, and the number and name of classes changes on time to time. Every update you'd have to add the new classes' name to the proxy. – Michele Tuloski Furci Jun 11 '21 at 09:51
40

Store the classes in an Object, with the keys being the names of the classes you want them to be.

const classesMapping = {
  'One': ClassOne,
  'Two': ClassTwo
};

then create the class based on the key name like this

return new classesMapping[desiredSubclassName](args);
thefourtheye
  • 233,700
  • 52
  • 457
  • 497