Is there any way to have dynamic object properties in a TypeScript class, and add dynamic Typings in for TypeScript?
I have seen similar questions but none with a complete example like this -
interface IHasObjectName {
objectName: string;
}
class example<A extends IHasObjectName, B extends IHasObjectName> {
constructor(a: A, b: B) {
this[a.objectName] = function() { return a; };
this[b.objectName] = function() { return b; }
}
}
class Cat implements IHasObjectName {
objectName: string = "";
}
class Dog implements IHasObjectName {
objectName: string = "";
}
let cat = new Cat();
cat.objectName = "Cat";
let dog = new Dog();
dog.objectName = "Dog";
let test = new example<Cat,Dog>(cat, dog);
// ??? TYPESCRIPT DOESN'T KNOW ABOUT THESE DYNAMIC PROPERTIES
// HOW DO I MAKE THIS WORK?
let d = test.Dog();
let c = test.Cat();
// I know I could access like this
// let d = test["Dog"]();
// but I want to access like function and have it typed
You can use a factory function and intersection:
function factory<A extends IHasObjectName, B extends IHasObjectName, C>(a: A, b: B): example<A, B> & C {
return new example<Cat, Dog>(a, b) as C;
}
var test = factory<Cat, Dog, { Dog(): Dog, Cat(): Cat }>(cat, dog);
var d = test.Dog(); // no error
var c = test.Cat(); // no error
(code in playground)
Edit
You can't "reflect" types because they don't exist in runtime, but you can use the constructor.name of the passed in instances, so you can simply do this:
class example<A, B> {
constructor(a: A, b: B) {
this[a.constructor.name] = function() { return a; };
this[b.constructor.name] = function() { return b; }
}
}
class Cat {}
class Dog {}
var cat = new Cat();
var dog = new Dog();
function factory<A, B, C>(a: A, b: B): example<A, B> & C {
return new example<Cat, Dog>(a, b) as C;
}
var test = factory<Cat, Dog, { Dog(): Dog, Cat(): Cat }>(cat, dog);
var d = test.Dog();
var c = test.Cat();
(code in playground)
You need to cast it to any object type if you wan't "JavaScript behavior" in TypeScript.
There are two syntax types, which are equivalent:
const d = (<any>test).Dog();
const c = (<any>test).Cat();
and
const testAny = test as any;
const d = testAny.Dog();
const c = testAny.Cat();
the last one was created for support in tsx files, where wouldn't work and is now the recommended way.
There is hardly an other way to do that other than use the indexer, since the properties are dynamic and not typed.
BTW I encourage to use const and let instead of var.
Related
I've put together a simplified example of what I'm trying to do, obviously a bit contrived... I have this class:
export class myClass {
a = 'bar';
b = 0;
save(x: any = null): void {
//save all properties
//...
}
}
In other classes that need to use it, I will define foo = new myClass();
Then it can be used either as:
this.foo.b = 3
this.foo.save();
or, because sometimes I just want it on one line (hence the x: any = null:
this.foo.save(this.foo.b = 3);
I would like to write the single line version more elegantly, and feel something like this should be possible... is it?
//How can I make this possible?
this.foo.save(c => c.b = 3)
if it is possible, what would the add method look like?
Many thanks!
Answer for the original question.
If you want this.calc.add(c => c.b = 3), then you need to handle invoking the function c => c.b = 3 once passed to the add method.
So just check the value is a function, if it is then pass this to the function, which would be c in your function, then the return value you add with this.b
Plain old js.
class Calculator {
constructor() {
this.a = 10
this.b = 0
this.sum = 0
}
add(x) {
this.sum = this.a + (typeof x === 'function' ? x(this) : x)
}
}
const calc = new Calculator()
calc.add(c => c.b = 3)
console.log(calc.sum)
calc.add(1)
console.log(calc.sum)
Implicitly assigning is anti pattern
// Something that you should avoid
this.calc.b = 3
class Calc {
constructor(private a: number = 0, private b: number = 0) {}
setA(a: number) {
this.a = a;
return this;
}
setB(b: number) {
this.b = b;
return this;
}
sum() {
return this.a + this.b;
}
}
const calc = new Calc();
// will return 0
console.log(calc.sum());
// will return 6
console.log(calc.setA(1).setB(5).sum());
const calc1 = new Calc(1,2);
// will return 3
console.log(calc1.sum());
Consider the following scenario:
class A {
constructor() {
this.A = 'A';
}
createB() {
//Create a B instance from this current instance
}
}
class B extends A {
constructor() {
super();
this.B = 'B';
}
}
var base = new A();
var derived = new B();
base.A = 'C';
// Create a B instance from A so that
// a new A isn't instantiated and
// fromInstance.A === 'C'
var fromInstance = base.createB();
I would like to be able to create an instance of B without having to create a new instance of A, but rather use the existing A instance.
My goal is to be able to spawn a B instance by calling a function within A, but also allow a B instance to be created directly and handle constructing a default A.
How can I achieve something like this when B extends A and requires super() to be called?
Not sure this is exactly what you want but it works for your example:
class A {
constructor() {
this.A = 'A';
}
}
class B extends A {
constructor() {
super();
this.B = 'B';
}
}
var base = new A();
var derived = new B();
base.A = 'C';
// Create a B instance from A so that
// a new A isn't instantiated and
// fromInstance.A === 'C'
var fromInstance = new B();
Object.assign(fromInstance, base);
console.log(fromInstance);
Here is an alternate solution. It is actually pretty common in C# and Java, but since JS has no method overloading, this is kind of cumbersome and not so nice compared to the above solution:
class A {
constructor(source) {
if(source){
//use properties/stuff from source
this.A=source.A;
}
else{
//only perform initialization if source is not provided
this.A = 'A';
}
}
}
class B extends A {
constructor(source) {
super(source);
this.B = 'B';
}
}
var base = new A();
var derived = new B();
base.A = 'C';
// Create a B instance from A so that
// a new A isn't instantiated and
// fromInstance.A === 'C'
var fromInstance = new B(base);
console.log(fromInstance);
Basically, there are two versions of the constructor, one that creates a completely new object, and one that pretty much copies an old object.
I think there is a bit of a misunderstanding, every instance of B is by definition an instance of A, no matter what you do. If you want super to be called, you are calling the constructor of A, and thus "instantiating" A.
If I understand the problem, you want the new instance's A property to reflect the A property of the instance that created it, right? You can set this in createB since it will be called on A instance. This will allow the B instance to have a property that shadows the inherited A
class A {
constructor() {
this.A = 'A';
}
createB() {
let b = new B()
b.A = this.A // set to the instances 'A'
return b
}
}
class B extends A {
constructor() {
super();
this.B = 'B';
}
}
var base = new A();
var derived = new B();
base.A = 'C';
// Create a B instance from A so that
// a new A isn't instantiated and
// fromInstance.A === 'C'
var fromInstance = base.createB();
console.log(fromInstance)
A generic approach could copy all A instance properties to a new B instance. An even more generic approach would be to copy the B object's properties to temporary storage first, and write them back later, so that if A and B had the same property name, the B object's property would take precedence:
class A {
constructor() {
this.A = 'A';
}
createB() {
let b = new B();
let temp = {};
let aProp, bProp;
// save own properties of b
Object.keys( b).forEach( bProp => (temp[bProp]=b[bProp]));
// install own properties of A instance
Object.keys( this).forEach( aProp => ( b[aProp]=this[aProp]));
// write back b properties over A instance properties
Object.keys( temp).forEach( bProp=> (this[bProp]=temp[bProp]));
return b;
}
}
class B extends A {
constructor() {
super();
this.B = 'B';
}
}
var base = new A();
var derived = new B();
base.A = 'C';
// Create a B instance from A so that
// a new A isn't instantiated and
// fromInstance.A === 'C'
var fromInstance = base.createB();
console.log( "derived.A = %s", derived.A);
console.log( "fromInstance.A = %s", fromInstance.A);
Note that in JavaScript terms avoiding Class constructs be syntactically easier because you can change the prototype properties of ordinary functions but not Class constructor functions. However you would probably lose the ability to reliably identify B instances using instanceof B. Using a class expression in CreateB has the same problem - returned objects would not share the same Class constructor.
Just create an instance of B, copy the properties of the current instance of A to it using Object.assign then return it:
createB() {
var b = new B();
Object.assign(b, this);
return b;
}
Example:
class A {
constructor() {
this.A = 'A';
}
createB() {
var b = new B();
Object.assign(b, this);
return b;
}
}
class B extends A {
constructor() {
super();
this.B = 'B';
}
}
var base = new A();
var derived = new B();
base.A = 'C';
var fromInstance = base.createB();
console.log(fromInstance instanceof B);
console.log(fromInstance.A);
I have the JavaScript snippet below. Simply put, what am trying to achieve is; A way to check if a parameter passed to a function is an instance of some predetermined classes. I know I can use if(obj instanceof className){ /* do stuff * / } else{ /* other things */ } statements but it would be bulky code, especially if I have a bunch of classes to test against. To cut the story short, how can I achieve what am trying to do with the code below? Thanks all.
class A {
constructor(name) {
this._name = name;
}
}
class B {
constructor(name) {
this._name = name;
}
}
class C {
constructor(name) {
this._name = name;
}
}
let allTemplates = ['A', 'B', 'C', 'Object']; //available classes
let a = new A('A class');
let b = new B('B class');
let c = new C('C class');
function seekTemplateOf(obj) {
/**find if #arg obj is an instance of any
** of the classes above or just an object
**#return string "class that obj is instance of"
**/
return allTemplates.find(function(template) {
return obj instanceof window[template];
/*Thought that ^^ could do the trick?*/
});
}
console.log(seekTemplateOf(a));
/*"^^ Uncaught TypeError: Right-hand side of 'instanceof' is not an object"*/
You could use an object for the templates and check againt the given objects.
class A { constructor(name) { this._name=name; } }
class B { constructor(name) { this._name=name; } }
class C { constructor(name) { this._name=name; } }
let allTemplates = { A, B, C, Object };
let a = new A('A class');
let b = new B('B class');
let c = new C('C class');
function seekTemplateOf(obj) {
return Object.keys(allTemplates).find(template => obj instanceof allTemplates[template]);
}
console.log(seekTemplateOf(a));
Change your strings to references:
let allTemplates = [A, B, C, Object];
Then check if the objects constructor is equal to that:
const obj = new A;
const objClass = allTemplates.find(c => obj.constructor === c);
Alternatively ( if some prototype hacker forgot to set constructor) you might get the definite prototype:
const obj = new A;
const objClass = allTemplates.find(c => Object.getPrototypeOf(obj) === c.prototype);
Or you might simply use instanceof then:
const obj = new A;
const objClass = allTemplates.find(c => obj instanceof c);
The following question was asked in a JavaScript interview.
After creating 3 instances of a class, how to prevent further instance creation?
What is the answer for this?
I'm assuming the question requires that you get "clever" and not use any globals nor other classes.
You can use a static method to keep track of created instances. From then on you can throw errors in the constructor to prevent instantiation.
class Foo {
constructor(name) {
if (Foo.maxInstancesReached())
throw 'Max instances reached'
this.name = name
}
static maxInstancesReached() {
if (!this.numOfCreatedInstances)
this.numOfCreatedInstances = 0
return ++this.numOfCreatedInstances > 3
}
}
const foo1 = new Foo('Jack')
const foo2 = new Foo('John')
const foo3 = new Foo('Mary')
const foo4 = new Foo('Rebecca')
This could be achieved through the use of a factory function:
class Special {
}
let specialObjectCounter = 0;
function createSpecial() {
// factory function
if (specialObjectCounter === 3) return;
specialObjectCounter++;
return new Special();
}
const a = createSpecial();
const b = createSpecial();
const c = createSpecial();
const d = createSpecial();
const e = createSpecial();
console.log(a, b, c, d, e);
A more generic version would look like this:
function instanceLimiter(proto, count, action=()=>undefined) {
let counter = 0;
return function(...args) {
if (counter >= count) return action();
counter++;
return new proto(...args);
}
}
// Demo
class Special {
}
const createSpecial = instanceLimiter(Special, 3);
const res = Array.apply(null, Array(5)).map(createSpecial);
console.log(...res);
If you prefer throwing instead of returning undefined you can just pass a different action to instanceLimiter:
instanceLimiter(Special, 3, () => {throw 'Maximum instance count reached'});
Created a static variable and increment the count each time a new instance is created. If the count reaches the threshold, throw an error.
var Foo = function() {
if (Foo.instances >= 3) {
throw new Error("Max number of instances reached");
}
Foo.instances++;
};
Foo.instances = 0;
var a = new Foo();
console.log(a);
var b = new Foo();
console.log(b);
var c = new Foo();
console.log(c);
console.log(Foo.instances);
var d = new Foo();
console.log(d);
I'm trying to understand how inheritance works in JS. Suppose we have a class:
Class = function () {
this.A = 'A';
this.B = 'B';
};
and we are trying to extend it
SubClass = function () {};
SubClass.prototype = new Class();
Do I understance correctly that after inheritance properties A and B are common for all instances of SubClass, since they belong to it's prototype? If yes, how can Class be extended so that A and B do not be part of prototype?
UPD: note that Class uses A and B, so I can't declare them in SubClass.
Thank you in advance!
All I want is to make A and B be accessible and specific for each
"instance"
The typical way of doing this is to pass parameters and assign them to properties. Then you can use call to reference the super class. In other words:
function Person( name, age ) {
this.name = name;
this.age = age;
}
function Student( name, age, grade ) {
Person.call( this, name, age ); // call super-class with sub-class properties
this.grade = grade;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
var roger = new Student( 'Roger', 18, 'A+' );
You can use properties in parent class without defining:
Class = function () {
this.sum = function() {
return this.a+this.b;
}
};
SubClass = function () {
this.a = 5;
this.b = 6;
};
SubClass.prototype = new Class();
var z = new SubClass();
z.sum(); //11
Another way: Create function in prototype which creates your properties:
Class = function () {
this.makeAB = function() { //called with context of SubClass
this.A = 'A';
this.B = 'B';
}
};
SubClass = function () { this.makeAB() };
SubClass.prototype = new Class();
var z = new SubClass();
z.A = 'AAA';
z.B = 'BBB';
var z2 = new SubClass();
console.log(z)
console.log(z2)