Make setter an action using Mobx makeObservable in presence of getter - javascript

In mobx if I want to use interheritance I need to use makeObservable rather than makeAutoObservable. But using makeObservable requires I name the actions that mutate state so how can I declare a setter to be an action given it has the same name as the getter?
In other words what goes where I wrote SETTER_FOR_MYVAR or what is another way to achieve the same effect?
class BaseClass {
_myvar = null
set myvar(val) {
this._myvar = val;
}
get myvar() {
return this._myvar;
}
other_action() {
this._myvar = 5;
}
constructor() {
makeObservable(this, {
_myvar: observable,
other_action: action,
SETTER_FOR_MYVAR: action
});
}
}
Yes, I know I could farm it out to yet another helper function _myvar_setter and declare that an action but that seems ugly and I'm hoping there is a better way.

Just mark myvar as computed, everything should work out of the box (If I understand correctly what you want):
constructor() {
makeObservable(this, {
_myvar: observable,
myvar: computed,
other_action: action
});
}
Codesandbox
Excerpt from the docs:
It is possible to define a setter for computed values as well. Note that these setters cannot be used to alter the value of the computed property directly, but they can be used as an "inverse" of the derivation. Setters are automatically marked as actions.
Example:
class Dimension {
length = 2
constructor() {
makeAutoObservable(this)
}
get squared() {
return this.length * this.length
}
set squared(value) {
this.length = Math.sqrt(value)
}
}
More info in the docs

Related

Is my Vue.js computed function recursive?

I have a vue computed function and would like to know if there is a recursion in my code.
I have some comma seperated states within my prop this.searchQuery['by-multiple-states']. My computed function statesSearchQuery should be a 'wrapper' for my prop, it should just give me a way to work with my prop not as a string but as a array.
The getter just returns the states as array. The setter should toggle a given 'val'. If within the array, remove it, else add it. Finaly apply the changes on the prop itself.
Because the getter and setter is both called with this.statesSearchQuery and the getter is used within the first line of my setter, I'm not sure if there would be a weird interaction. Could someone enlighten me about this situation?
computed: {
statesSearchQuery: {
get() {
return this.searchQuery['by-multiple-states'].split(',').filter(t => t !== '');
},
set(val) {
let state = this.statesSearchQuery;
if (state.includes(val)) {
state = state.filter(t => t !== val);
} else {
state.push(val);
}
this.searchQuery['by-multiple-states'] = state.join(',');
},
},
},
Update: In retrospect, I can rephrase the question. Is it ok to use the getter of a computed function in the setter of the same?

JavaScript / TypeScript: Difference between `Object.assign({}, myClass)`/`Object.create(myClass)` and constructor `new MyClass()`

I wish I could post my project verbatim for this question, but I cannot.
Essentially, I have the following classes:
class Lowest {
someValue: string
constructor(someValue: string) {
this.someValue = someValue
}
}
class Middle {
lowest: Lowest
constructor(someValue: string) {
this.lowest = new Lowest(someValue)
}
}
class Highest {
private _middle: Middle
otherValue: SomeOtherClass
// normal getter
public get middle(): Middle {
return this._middle
}
public set middle(next: Middle) {
this._middle = next
// notice: when `_middle` is set, I update another property!
otherValue.doSomething(this._middle)
}
constructor(config: { someValue: string }) {
this.middle = new Middle(config.somevalue)
}
}
In some places in my program, I have a reference to a Highest instance and need to alter its middle.lowest.someValue sub-property. Now, for architectural reasons that I cannot really describe here, I need to update the Highest.otherValue property whenever Highest.middle. As I am using TypeScript, I simply perform this operation in the setter for Highest.middle. As such, I cannot just straight up set Highest.middle.lowest to some value.
My first approach was:
const nextMiddle = Object.assign({}, highestInstance.middle)
nextMiddle.lowest.someValue = "some other thing"
highestInstance.middle = nextMiddle
However, this ended up causing some very strange issues. Now, I had no real technical need for performing a deep clone of nextMiddle, so I overcame it with the following code:
const nextMiddle = highestInstance.middle
nextMiddle.lowest.someValue = "some other thing"
highestInstance.middle = nextMiddle
While I was experimenting with the best fix, I implemented a Middle.copy() method that basically just calls new Middle() and new Lowest() with the values from the previous instance. This also solved my technical issues, but left me even more confused.
I understand that there's a big difference between simply re-assigning highestInstance.middle and using Object.assign() to clone it, but I don't understand why there does not seem to be a difference between Object.assign() and new Middle()
What are the real differences with these three methods of cloning/re-assignment?
had no real technical need for performing a deep clone of nextMiddle,
so I overcame it with the following code:
Object.assign({}, highestInstance.middle) is creating a shallow copy and not a deep copy.
The issue here is using Middle Setter is forcing yourself to perform otherValue: SomeOtherClass only when middle is updated.
it will update it even if you just do just :
high.middle.lowest.someValue = 'new value'
high.middle = high.middle; // setter triggered
A possible solution is to create a call-back chain rather than using a setter:
type voidFun = () => void;
class Lowest {
private _someValue: string
public get someValue(): string {
return this._someValue
}
private update?: voidFun; // create an optional callback
public set someValue(next: string) {
this._someValue = next
this.update?.call(undefined);
}
constructor(someValue: string, fun?: voidFun) {
this._someValue = someValue
this.update = fun;// pass down
}
}
class Middle {
lowest: Lowest;
constructor(someValue: string, fun?: voidFun) {
this.lowest = new Lowest(someValue, fun)
}
}
class Highest {
private _middle: Middle
otherValue: any = {};
// normal getter
public get middle(): Middle {
return this._middle
}
// now you dont need middle setting so it cant be updated by anyone directly
// if you still want to have you can have it too
private callBack = () => {
console.log('lower was update');
this.otherValue.myMiddle = this._middle.lowest.someValue;
}
constructor(config: { someValue: string } = { someValue : 'constr' }) {
this._middle = new Middle(config.someValue, this.callBack)
}
}
let high = new Highest()
high.middle.lowest.someValue = 'new value'
// high.middle = high.middle; // now not possible unless you create setter
console.log('updated middle high',high)

Nested JS decorator get/set's, how to properly chain them?

The ember framework has adopted decorators aggressively. In order to utilize data binding now i have to decorate my properties with #tracked which gets me all my nice UI updates anytime i change a property.
#tracked username = 'dave';
This works well, but i'm encountering some serious problems if i need to add a custom decorator on top of the tracked decorator.
#typed(StateTrackMap)
#tracked
mapConfigsArray = [create(StateTrackMap)];
I'm able to get this to work by having my #typed decorator check to see if it is above another decorator or not.
export default function typed(classType) {
let weak = new WeakMap();
return function(object, property, descriptor) {
return {
get() {
// Check if there is another decorator attached below us in the chain
// i.e. "tracked"
if (typeof descriptor.get == 'function') {
return descriptor.get.call(this);
}
// If we haven't initialized before but there is one ready, return that
if (!weak.has(this) && typeof descriptor.initializer == 'function') {
weak.set(this, descriptor.initializer.call(this));
}
return weak.get(this);
},
set(value) {
// my set code which does the type checking/converting this descriptor is for
// Apply the converted value to the lower level object
// This may be the object itself, or it may be another setter in the chain
if (typeof descriptor.set == 'function') {
descriptor.set.call(this, typedValue);
} else {
return weak.set(this, typedValue);
}
}
}
}
}
But this feels, weird... and doesn't look like any of the usages of descriptors i've seen.
Mostly because if i change the order of the decorators things explode
#tracked
#typed(StateTrackMap)
mapConfigsArray = [create(StateTrackMap)];
index.js:172 Uncaught Error: Assertion Failed: The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both.
So i guess my question is, what is the proper way to chain decorators that have get & set? It seems to me that the order of the decorators determines if i can go up/down the chain or not. Also it seems to me that this chaining logic has to be baked into every decorator or else it doesn't work. Is there some generic way i can pass decorators to other decorators?
I've seen some examples where i return the descriptor reference but that doesn't appear to help the problem here either as i am not quite sure how i can still inject my get/set on it without erasing the property property chain or getting into the same boat as above where my code has to be designed to work with other descriptors specifically.
export default function typed(classType) {
return function(object, property, descriptor) {
const set = descriptor.set;
const get = descriptor.get;
const weak = new WeakMap();
descriptor.get = function() {
if (typeof get == 'function') {
return get.call(this);
}
// If we haven't initialized before but there is one ready, return that
if (!weak.has(this) && typeof descriptor.initializer == 'function') {
weak.set(this, descriptor.initializer.call(this));
}
return weak.get(this);
}
descriptor.set = function(value) {
// My type checking / conversion code
// Apply the converted value to the lower level object
// This may be the object itself, or it may be another setter in the chain
if (typeof set == 'function') {
set.call(this, typedValue);
} else {
return weak.set(this, typedValue);
}
}
return descriptor;
}
}
BTW this method gives a different explosion.
Assertion Failed: You attempted to use #tracked on mapConfigsArray, but that element is not a class field.

TypeScript: Overwrite instance function in unrelated class

I have an Item Class built like this
export class Item {
element:JQuery<HTMLElement>
constructor(...) {
this.element = $("<div >...</div>");
...
this._tooltipUpdate();
this.element.tooltip({
items: "div[data-tooltip]",
content: this.element.attr("data-tooltip"),
open: this._tooltipUpdate,
...
});
}
...
public _tooltipUpdate = ():void => {
this.element.attr(
"data-tooltip",
`...`
);
};
}
Basically, the Item Class has an element attribute that holds its DOM element.
I now have a different class, InventoryElement
export class InventoryElement extends MenuElement {
amount:number;
constructor(item:Item) {
super(...)
this.amount = 0;
...
this.item._tooltipUpdate = () => {
this.element.attr(
"data-tooltip",
`${this.amount}`
);
}
}
}
The Item instance of InventoryElement should have a different _tooltipUpdate function, basically.
Currently, it's not overwriting it correctly.
I had _tooltipUpdate on Item implemented like this before,
_tooltipUpdate() {
...
}
but I read that that would implement it as a prototype function instead of an instance function with the arrow operator above.
Am I using the arrow function properly? How can I change the function of the Item instance? Thanks!
When using arrow function, you are binding this to the calling instance of type InventoryElement.
If you want to call this.element in Item, you need to do
this.item._tooltipUpdate = function() {
// this is now bound to Item
}.bind(this.item);
See bind function definition
Generally I do not think the way you would do this, is the best way.
It seems like your _tooltipUpdate is something like a handler, that enables you to react upon a change within the item. It is always better to implement some event logic and then attach listeners to it.
Also, as I see, there is just amount that is changing. So why not just have a method within Item that says setAmount. That would be much cleaner and easier to implement.

ngOnInit and constructor who run first

When I call my service injected in the constructor, I get undefined.
the service is called in ngOnInit method, From Difference between Constructor and ngOnInit I have seen that constructor run first , but in my case I noted the opposite, so I'm bit confused. have someone more explication about that, thanks.
constructor(private curveService :ProgressCurveService, private util : UtilService) {
this.startPickerOptions = new DatePickerOptions();
this.endPickerOptions = new DatePickerOptions();
//this.datePickerOptions.initialDate = new Date(Date.now());
}
ngOnInit() {
this.curveService.instance.getCurve(this.startDate.formatted,this.endDate.formatted,this.amplutid).
then(res => {
this.lineChartLabels = this.util.dateToShortString(Object.keys(res.progressPlotData))
this.lineChartData = this.util.objectToIntArray(res.progressPlotData);
}).catch(res => console.log('error if date selecting ...'));
}
progress curve service:
import { progressCurveItf } from './progress-curve/progress-curve-interface';
#Injectable()
export class ProgressCurveService {
state : string = 'project';
constructor(private prsCurve : PrsProgressCurveService, private projCurve : ProjProgressCurveService) { }
get instance():progressCurveItf{
if(this.state == 'subproject'){
return this.prsCurve;
} else {
return this.projCurve;
}
}
}
While you're returning an instance of type progressCurveItf that is an interface I think there is something wrong with the instantiation of what you're returning,
check if you provide PrsProgressCurveService and ProjProgressCurveService.
To answer your question Constructor will get invoked since it belongs to ES6, basically which got the first priority. Where as ngOnInit is a life cycle hook designed by angular team which will get invoked after constructor even after ngOnChanges life cycle hook.
Constructor -> ngOnChanges -> ngOnInit -> followed by other life cycle hooks

Categories

Resources