How to merge options object passed in constructor as properties - javascript

I want to create a class that takes an options object as it's argument in its constructor. Each of the member of option object should become member of the class instance.
const __defaults = {
isDisabled = false,
caption: undefined
}
class Button {
constructor(options) {
this.caption = options.caption || __defaults.caption;
this.isDisabled = options.isDisabled || __defaults.disabled;
}
}
Is there a better way to handle this like spread operator?
this.config = { ...options, ...__defaultOptions };
The only problem is I can't assign directly to this using spread operator. That would be an invalid assignment.
this = { ...options, ...__defaultOptions };
I want to create all properties directly inside class and not within a config property in the instance. That way when I initialize my button in following manner...
const registerButton = new Button({ isDisabled: true });
I can read property just like this:
console.log(registerButton.isDisabled);
and not like this...
console.log(registerButton.config.isDisabled);
Former approach is more verbose and readable.

Use Object.assign()
class Foo{
constructor(options)
{
Object.assign(this,options);
}
}
let foo = new Foo({a:1,b:2,c:3});
console.log(foo);

Object.assign can assign the merged properties onto the instance.
constructor(options) {
Object.assign(
this,
__defaultOptions,
options
);
}

You can create a temp object i.e obj and then loop over the object keys and then add property on this
const __defaults = {
isDisabled: false,
caption: undefined,
};
class Button {
constructor(options) {
const obj = { ...options, ...__defaults };
Object.keys(obj).forEach((k) => (this[k] = obj[k]));
}
}
const registerButton = new Button({
isDisabled: true
});
console.log(registerButton);

Related

How to keep object properties default values

I want to know how do keep initialed values of object properties
class MyClass{
constructor(){
this.settings = {
theme: 'dark',
confirmBtn: 'ok',
cancelBtn: 'cancel',
}
}
}
let obj= new MyClass();
obj.settings = {
theme: 'white'
}
and other properties like confirmBtn keep their values that assigned in the class declaration.
You have a couple of options.
Only assign to the one property:
obj.settings.theme = "white";
Use Object.assign if you have an object of properties you want to assign (here I have just the one property, but you could have more):
Object.assign(obj.settings, {
theme: "white",
});
Object.assign copies all of the own, enumerable properties from the objects provided as the second argument onward into the object provided as the first argument.
Use object spread syntax ... on both the existing obj.settings object and the new settings object into an anonymous, new object and assign that to obj.settings:
obj.settings = { ...obj.settings, ...{ theme: "white" } };
Though there's no reason to do that here, just update the settings object you have. This option can be very useful in situations where you want to avoid ever modifying the existing object (state updates in various MVC libs, etc.).
In a self-answer it appears you wanted to move the assignment into the constructor, which you didn't mention in the question. I'd handle that by using parameter destructuring combined with destructuring default values and a parameter default (just a blank object):
class MyClass {
constructor({theme = "dark", confirmBtn = "ok", cancelBtn = "cancel"} = {}) {
this.settings = {
theme,
confirmBtn,
cancelBtn,
};
}
}
let obj= new MyClass({
theme: "white",
});
Live Example:
class MyClass {
constructor({theme = "dark", confirmBtn = "ok", cancelBtn = "cancel"} = {}) {
this.settings = {
theme,
confirmBtn,
cancelBtn,
};
}
}
// Settings not specified get defaulted
const obj1 = new MyClass({
theme: "white",
});
console.log(obj1.settings);
// No parameter at all gets all defaults
const obj2 = new MyClass();
console.log(obj2.settings);
You can also do it with Object.assign, though I like the expressiveness of the defaults all being in the constructor signature:
class MyClass {
constructor(settings = {}) {
this.settings = Object.assign(
{},
{
theme: "dark",
confirmBtn: "ok",
cancelBtn: "cancel",
},
settings
);
}
}
let obj= new MyClass({
theme: "white",
});
Live Example:
class MyClass {
constructor(settings = {}) {
this.settings = Object.assign(
{},
{
theme: "dark",
confirmBtn: "ok",
cancelBtn: "cancel",
},
settings
);
}
}
// Settings not specified get defaulted
const obj1 = new MyClass({
theme: "white",
});
console.log(obj1.settings);
// No parameter at all gets all defaults
const obj2 = new MyClass();
console.log(obj2.settings);
You could use the private class feature and use getters and setters.
class MyClass{
#settings;
constractor(){
this.#settings={
theme:'dark',
confirmBtn:'ok',
cancelBtn:'cancel'
}
}
getSettings(){
return this.#settings;
}
}
trying to access #setting swill yield an error
I have solved my issue by JQuery $.extend:
I defined a deafult object and get options as another object and merged them.
class MyClass{
constructor(options){
this.default = {
theme: 'dark',
confirmBtn: 'ok',
cancelBtn: 'cancel',
}
this.settings=$.extend({},this.default,options);
}
}
let obj= new MyClass({
theme:'dark
});

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

use plainToClass in constructor

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'

Default Object Property

I am playing with ES6 classes and to better manage an array property of the class, I replaced the array with an object and added all the array-related functions (get, add, remove, etc) along with an array sub-property:
class MyClass {
constructor () {
this.date_created = new Date()
}
posts = {
items: [],
get: () => {
return this.posts.items
},
add: (value) => this.posts.items.unshift(value),
remove: (index) => this.posts.items.splice(index, 1)
}
}
So it got me thinking: is there a way to setup that posts object to return the items array by default? i.e. through: MyClass.posts
I thought I could trick it with the get() but didn't work.
If you want to keep the actual array hidden and untouchable except through the methods, it has to be declared in the constructor. Any function that alters it has to be declared in the constructor as well. If that item is to be publicly accessible, it has to be attached to this.
class Post extends Array
{
add(val)
{
this.unshift(val);
}
remove()
{
this.shift();
}
}
class MyClass
{
constructor()
{
this.date_created = new Date()
this.post = new Post();
}
}
let x = new MyClass();
console.log(x.post);
x.post.add(2);
console.log(x.post);
class MyClass {
constructor() {
this.post = [];
}
get post() {
return [1,2,3]
}
set post(val) {
}
}
let someClass = new MyClass();
console.log(someClass.post)
I believe the correct syntax is something as above. your getter and setter is pointing to post, where post is only a variable/key of MyClass, hence your getter and setter should be at MyClass level

JS immutable objects keep the same object type

Let's say I have a class.
class Test {
constructor() {
this.name = 'name';
this.arr = [];
}
}
And I need to create multiple instances from this class.
const entities = {
1: new Test(),
2: new Test()
}
Now, I need to update one of the properties in a shallow clone manner.
const newEntities = {
...entities,
[1]: {
...entities[1],
name: 'changed'
}
}
console.log(newEntities[1].arr === entities[1].arr) <=== true
That's works, but the problem is that [1] is a simple object and not instance of Test anymore.
How can I fix that?
You can't keep instances using object destructuring so you'll need implement this behaviour.
First example, set the new properties in the constructor:
class Test {
constructor(props) {
props = props == null ? {} : props;
this.name = 'name';
this.arr = [];
Object.assign(this, props);
}
}
const newEntities = {
...entities,
[1]: new Test({ ...entities[1], name: 'changed' })
}
Sencod example, use a custom method:
class Test {
constructor() {
this.name = 'name';
this.arr = [];
}
assign(props) {
props = props == null ? {} : props;
const instance = new Test();
Object.assign(instance, this, props);
return instance;
}
}
const newEntities = {
...entities,
[1]: entities[1].assign({ name: 'changed' })
}
You can use Object.setPrototypeOf on your [1] object.
As result, it will be:
Object.setPrototypeOf(newEntities[1], Test.prototype);

Categories

Resources