3

I have a Node.js v11.11.0 app. In this app, my files are structured like the following:

./src
  /animals/
    animal.js
    tiger.js
    koala.js
  index.js

As shown above, I have three classes defined in the animals directory. Over time, I intend to add additional animals with more complex logic. At this time, my classes are defined like so:

animal.js

'use strict';

class Animal {
  constructor(properties) {
    properties = properties || {};

    this.kind = 'Unknown';
  }

  eat(foods) {
    for (let i=0; i<foods.length; i++) {
      console.log(`Eating ${foods[i]}`);
    }
  }
}

module.exports = Animal;

tiger.js

'use strict';

const Animal = require('./animal');

class Tiger extends Animal {
  constructor(properties) {
    super(properties);

    this.kind = 'Tiger';
  }

  eat(foods) {
    for (let i=0; i<foods.length; i++) {
      if (foods[i].kind === 'meat') {
        console.log(`Eating ${foods[i]}`);
      }
    }
  }
}

module.exports = Tiger;

koala.js

'use strict';

const Animal = require('./animal');

class Koala extends Animal {
  constructor(properties) {
    super(properties);

    this.kind = 'Koala';
  }

  eat(foods) {
    for (let i=0; i<foods.length; i++) {
      if (foods[i].kind === 'plant') {
        console.log(`Eating ${foods[i]}`);
      }
    }
  }
}

module.exports = Koala;

I am trying to dynamically create an instance of one of these classes based on what the user enters. For example, I'm doing this:

index.js

const readline = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
})

readline.question('What animal file do you want to load?', (fname) => {
  let properties = {
    name: 'My Pet'
  };

  let Animal = require(fname);
  let pet = Object.create(Animal.prototype, properties);
});

If I run this and enter ./animals/tiger.js, I get an error. The error says: Property description must be an object. I don't understand this. Both Tiger and Koala extend Animal. At the same time, as my list will be dynamic, I need the free form option of typing a file path and dynamically loading the class. For that reason, I can't use the approach shown here, which specifically lists the Class names.

How do I dynamically load a class and create an instance of that class with specific properties?

user687554
  • 10,663
  • 25
  • 77
  • 138
  • Possible duplicate of ["Property description must be an object" error in javascript. Can't understand why :(](https://stackoverflow.com/questions/12032262/property-description-must-be-an-object-error-in-javascript-cant-understand-w) – James Mar 21 '19 at 12:03

1 Answers1

5

The second argument to Object.create is the same argument you would pass to Object.defineProperties, your properties is therefore invalid. Instead use Object.assign:

 Object.assign(Object.create(Animal.prototype), properties)

But why don't you just call the constructor?

 new Animal(properties)

Secondly this:

 properties = properties || {};

is probably intended to be:

 this.properties = properties || {};

And I wouldn't recommend to dynamically require, especially not from a user entered value, instead create a lookup object:

 const AnimalByName = {
   Bear: require("./Bear"),
   //...
 };

 const instance = new AnimalByName[name](properties);
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • +1 for the constructor point. Regarding your last point, what is the point of the lookup object? I don't think about "performances" as OP also requires dynamically. Is it for "UI" reasons (user needs to enter `Bear` instead of `./animals/Bear.js`? – Al-un Mar 21 '19 at 12:03
  • 2
    @al-un there are various reasons, one would be the following animal: `../.../../secret/directory/password.json` ... additionally there are a lot of cases where the require would crash or create an invalid Animal, which then causes trouble later on. – Jonas Wilms Mar 21 '19 at 12:08
  • The `new Animal(properties)` approach worked. Thank you. – user687554 Mar 21 '19 at 13:13