2

It seems like you should be able to do this because building a form dynamically based off of a class definition (Angular) would work so much better if the logic could be written agnostically of the class. This would be scalable, so an addition of a field to the class would not also require an update to the logic producing the form and template.

Is there any way to do this or even an NPM module that will do this?

I found that I can do ClassName.toString() but it would be a pain to parse that. And I just might write a module to do it if nothing else.

I just feel like instantiating a dummy instance of the class for the purpose of enumerating over its properties is a poor strategy.

NAMS
  • 983
  • 7
  • 17
  • This is how JavaScript works. There are no classes, there are only functions, objects and prototypes. An object's properties can be defined in its constructor. Given `function Test() { this.prop = 3 }; x = new Test()` how are you going to know that `x` has the property `prop` without running the constructor? – user229044 Mar 16 '18 at 22:15

3 Answers3

1

You could use Object.getOwnPropertyNames().

Example class:

class Foo { setBar() { throw Error('not implemented'); return false; } getBar() { throw Error('not implemented'); return false; } }

And then

Object.getOwnPropertyNames(Foo.prototype)

results in

["constructor", "setBar", "getBar"]

While I was researching this I looked into Object.keys first, and although it didn't work, you may wish to reference the documentation for Object.keys's polyfill. It has code for stripping out constructor, toString, and the like, as well as properly implementing hasOwnProperty.

Also see Bergi's answer here.

Graham P Heath
  • 7,009
  • 3
  • 31
  • 45
0

Any way? Declare your class as a function, and put the properties on the prototype:

var Presidents = function() {
};

Presidents.prototype = {
  "washington" : "george",
  "adams" : "john"
}

console.log(Object.keys(Presidents.prototype))

// Output is
//  [ 'washington', 'adams' ]
SaganRitual
  • 3,143
  • 2
  • 24
  • 40
  • 3
    `class` is already a function, and it's methods *are* on the prototype. – user229044 Mar 16 '18 at 22:20
  • You're just going to tell me I'm wrong, without telling me the proper way to say it? That's cold. – SaganRitual Mar 16 '18 at 22:22
  • I didn't say you were wrong, I'm just pointing out that `class presidents` and `function presidents` are the same thing. – user229044 Mar 16 '18 at 22:24
  • Ah, we're going on specific keywords, are we? I'll rephrase then: You're just going to *point out* that I *said something that doesn't entirely make sense in this context*, without telling me *a more sensible way to say it*? That's *unfriendly*. – SaganRitual Mar 16 '18 at 22:27
  • 2
    No part of my comment was meant to be unfriendly, and I certainly don’t have to have an alternative lined up to comment in or critique your answer. – user229044 Mar 16 '18 at 22:48
0

getOwnPropertyDescriptors of a class prototype will only expose methods and accessor descriptors - data properties can not be determined without instantiation (also because constructor arguments can influence the amount, types and values of props). There can be several reasons to not want to instantiate (e.g. because some static counter tracks instances) - therefore a workaround could be to dynamically create a copy of the class and instatiate that "shadow" along with sample constructor arguments.

    /**
     * get properties from class definition without instantiating it
     *
     * @param cls: class
     * @param args: sample arguments to pass to shadow constructor
     * @usage `const props = getInstanceProperties(MyClass);`
     * @notice this will regex replace the classname (can be an issue with strings containing that substring)
     */
    const getInstanceProperties = (cls, args = [], ignore = ['constructor', 'toString']) => {
        const className = cls.prototype.constructor.name;
        const shadowCode = cls.toString().replace(new RegExp(`${className}`, 'g'), `_${className}_`);
        const shadowClass = eval(`(${shadowCode})`);
        const o = new shadowClass(...args);
        const methodsAndAccessors = Object.getOwnPropertyDescriptors(cls.prototype);
        const dataDescriptors = Object.getOwnPropertyDescriptors(o);
        const descriptors = Object.assign({}, methodsAndAccessors, dataDescriptors);
        ignore.forEach(name => delete descriptors[name]);
        return descriptors;
    };


    class Foo extends Object {
      static instances = 0;
      #myPrivateVar = 123;
      myValue=123;
      constructor(){
          super();
          this.myConstructorVar = ++Foo.instances;
      }
      myMethod() {}
      set myAccessor(x){}
    }

    console.log(Object.keys(getInstanceProperties(Foo)));

will return: [ 'myMethod', 'myAccessor', 'myValue', 'myConstructorProp' ]

Ian Carter
  • 548
  • 8
  • 7
  • great effort but it sounds a bit scary to me. what about classes that have global side-effect on construct? it feels like it doesn't instantiating (the whole point is that) but indeed does and that can be a reason for a hair-pulling bugs – Vagif VALIYEV Jul 12 '23 at 14:24