use plainToClass in constructor - javascript

I have a constructor that assigns properties to the instance:
class BaseModel {
constructor (args = {}) {
for (let key in args) {
this[key] = args[key]
}
}
}
class User extends BaseModel {
name: string
}
Then I can create an instance like this:
let user = new User({name: 'Jon'})
I would now like to replace this basic functionality with class-transformer:
let user = plainToClass(User, {name: 'Jon'})
My codebase uses the first approach in many places and I would therefore like to implement the new way in the constructor so I don't break the old code:
constructor (args = {}) {
let instance = plainToClass(CLASSTYPE, args)
for (let key in Object.keys(instance)) {
this[key] = instance [key]
}
}
How can I get the class type in the constructor? I cannot use User because this is the base model and other classes may also extend from it.

I suppose it's new.target. Doc
To get constructor use new.target.prototype.constructor, and it's property name - to get class name.
class BaseModel {
typeName: string;
constructor (args = {}) {
this.typeName = new.taget.constructor.name;
}
}
class User extends BaseModel {
}
const user = new User();
const typeName = user.typeName; // this would return 'User'

Related

typescript class to throw error when undefined is passed as a parameter to constructor

I have a class with a lot of parameters, a simplified version is shown below:
class data {
ID: string;
desp: string;
constructor(con_ID:string,con_desp:string){
this.ID = con_ID;
this.desp = con_desp;
}
}
I am then receiving data from a RESTful call, the body of the call is JSON. It might not have all the parameters requried to create an instance of data. Below is an example of the desp not being passed.
const a = JSON.stringify({ ID: 'bob' });
const b = JSON.parse(a)
If I try to create a new instance of data, it works.
console.log(new data(b['ID'], b['desp']))
>> data { ID: undefined, desp: 'bob' }
How do I reject the construction of the class if a parameter from JSON is undefined?
One method would be to do this for each parameter within the constructor, but I don't think this is the correct solution:
if (con_ID== undefined){
throw new Error('con_ID is undefined')
}
We can utilize class decorators for this. If we return a class from the decorator then the class' constructor will replace the one defined in code. Then we use parameter decorators to store the index of each parameter we wish to check into an array.
const noUndefinedKey = Symbol("noUndefinedKey");
const NoUndefined: ParameterDecorator = function (target, key, index) {
const data = Reflect.getMetadata(noUndefinedKey, target) ?? [];
data.push(index);
Reflect.defineMetadata(noUndefinedKey, data, target);
};
const Validate = function (target: { new (...args: any[]): any }) {
const data = Reflect.getMetadata(noUndefinedKey, target);
return class extends target {
constructor(...args: any[]) {
data.forEach((index: number) => {
if (typeof args[index] === "undefined") throw new TypeError(`Cannot be undefined.`);
});
super(...args);
}
}
}
Note that reflect-metadata must be used to use Reflect.getMetadata and Reflect.defineMetadata. Here's how you would use it:
#Validate
class ProtectedFromUndefined {
constructor(#NoUndefined param: string) {}
}
And try a few things:
//#ts-ignore throws error because undefined was provided
new ProtectedFromUndefined()
//#ts-ignore
new ProtectedFromUndefined(undefined)
// is ok
new ProtectedFromUndefined("")
Playground

How to read instance decorators in typescript?

I can create custom decorator using reflect-metadata and it work fine.
Problem is, that I don`t know how to get all instance decorators.
import 'reflect-metadata';
console.clear();
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
const args = Reflect.getMetadata(
'design:paramtypes',
target,
propertyKey
).map(c => c.name);
const ret = Reflect.getMetadata('design:returntype', target, propertyKey);
console.log(`Arguments type: ${args.join(', ')}.`);
console.log(`Return type: ${ret.name}.`);
};
return decorator;
}
class Foo {}
class Bar {
#readTypes()
public fn(a: number, b: string, c: Foo): boolean {
return true;
}
}
const barInstance = new Bar();
I would like to get all functions with decorator #readTypes from barInstance. How can I do it?
See working example:
https://stackblitz.com/edit/decorators-metadata-example-nakg4c
First of all, you aren't writing any metadata, only reading it. If you want to lookup which properties were decorated, then you have to write metadata to those properties.
For simplicity's sake, let's simplify the decorator to:
// It's a best practice to use symbol as metadata keys.
const isReadTypesProp = Symbol('isReadTypesProp')
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
Reflect.defineMetadata(isReadTypesProp, true, target, propertyKey);
};
return decorator;
}
Now when the decorator is used, it's executed when the class is parsed, not when instances are created. This means that target in the decorator function is actually the prototype of the class constructor function.
In other words target is the same object as
Bar.prototype or barInstance.constructor.prototype or barInstance.__proto__.
Knowing that we can loop over all the property names in the prototype object and look up that metadata we set earlier:
function getReadTypesPropsFromInstance(object: {
constructor: {
prototype: unknown;
};
}) {
const target = object.constructor.prototype;
const keys = Object.getOwnPropertyNames(target);
return keys.filter(key => Reflect.getMetadata(isReadTypesProp, target, key));
}
Which now returns the names of the decorated properties:
const barInstance = new Bar();
console.log(getReadTypesPropsFromInstance(barInstance)); // ["fn"]
Working example

TypeScript generics create instace

I'm trying to create a factory for instantiating my classes with generics. Checked out TypeScript docs and it all works perfectly. In short, this works just fine:
class Person {
firstName = 'John';
lastName = 'Doe';
}
class Factory {
create<T>(type: (new () => T)): T {
return new type();
}
}
let factory = new Factory();
let person = factory.create(Person);
console.log(JSON.stringify(person));
Now define class Person in directory:
export class Person extends BasePerson {
firstName = 'John';
lastName = 'Doe';
}
And when I import Person from other package:
import { Person } from "./directory"
class Factory {
create<T>(type: (new () => T)): T {
return new type();
}
}
let factory = new Factory();
let person = factory.create(Person);
I get error:
Argument of type 'typeof Person' is not assignable to parameter of type 'new () => Person'
How can I get a value of Person instead of typeof Person?
Using TypeScript 3.7.2 and Node v10.13.0.
Could you try this for me please?
import { Person } from "./directory"
class Factory {
create<T>(type: (new () => T)): T {
return new type();
}
}
let factory = new Factory();
let person = factory.create(new Person);
Actual problem here was a parent class of Person -> BasePerson. BasePerson expected an argument in its constructor, so when I tried to call factory.create(Person), Person actually was an typeof because it expected an argument for base class constructor. Problem was solved by deleting the constructor in base class and injecting a property via ioc container, in my case Inversify.

Can we get the attributes of a javascript class?

I have this javascript class :
class UserDTO {
constructor(props) {
this.username = props.username;
this.birthday = props.birthday;
}
}
and I have a class Utils that convert an Entity to DTO:
class Utils {
convertEntityToDTO (entityObj, DTOClass) {
// entityObj is an instance of a Entity,
// DTOClass is a class not an instance
let objDTO = new DTOClass();
Object.getOwnPropertyNames(entityObj)
.filter(prop => DTOClass.hasOwnProperty(prop))
.forEach(prop => {
objDTO[prop] = entityObj[prop];
});
}
}
this doesn't work a class ; hasOwnProperty just work with object; is a way to verify if a property is an attribute of a class or not ? or I have to create an instance to test ?
You can use hasOwnProperty on the instance and getOwnPropertyNames :
class A {
constructor() {
this.ex = 'TEST';
}
}
var a = new A();
console.log(a.hasOwnProperty('ex'));
console.log(Object.getOwnPropertyNames(a));
If instead you want the methods, you need to get the prototype:
class B {
constructor() {}
exMethod() {
console.log('test');
}
}
var b = new B();
console.log(Object.getPrototypeOf(b).hasOwnProperty('exMethod'));
console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(b)));

es6 class pass this to a static class functions

i have this class
import { ObjectUtilities } from '~/utils/';
class Object{
constructor(object) {
Object.assign(this, { ...object });
}
utils = ObjectUtilities;
}
and this class with the statis method also (class contains many static methods)
class ObjectUtilities {
static getKey(object){
return object.key;
}
}
and i want to know if its possible to share the "this" from the Object class
to the static method "getKey(object)"
want to do it as:
let x = new Object(object);
x.utils.getkey(this);
(ObjectUtilities as many static funcs i dont want to do it for each of them)
thanks for helping me out...
You can add a constructor to the ObjectUtilities class where you bind the given context to the getKey function:
class ObjectUtilities {
constructor(_this) {
this.getKey = this.getKey.bind(_this);
}
getKey() {
return this.key;
}
}
class MyObject {
constructor(object) {
Object.assign(this, { ...object });
this.utils = new ObjectUtilities(this);
}
}
const objectFoo = { key: 'foo' };
const objectBar = { key: 'bar' };
let x = new MyObject(objectFoo);
let y = new MyObject(objectBar);
console.log(x.utils.getKey(), y.utils.getKey());

Categories

Resources