Vue 3 - Make the specific class properties reactive - vue.js

Is there any possibility to make some properties of the class instance reactive?
In the MobX it's fairly easy to do:
class Doubler {
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
})
this.value = value
}
get double() {
return this.value * 2
}
}
But it looks like impossible to do it in Vue.
1.The most closest result that I get is the following result:
class Doubler {
constructor(value) {
this.value = ref(value)
this.double = computed(() => this.value.value * 2) // Ugly
}
}
The computed code is ugly and it's using also differs:
const doubler = new Doubler(1)
double.value = 2 // No way!
double.value.value = 2 // That's it! Ugly, but that's it.
2.I can pass the created object to reactive function, but it make all properties reactive and it doesn't affect the internal implementation and it still will be ugly.
Is there any way to reproduce MobX approach in Vue?

I don't think you can achieve it with classes. With objects though, the closest thing I can think of is something like this:
function createDoubler(value) {
const doubler = reactive({ value })
doubler.double = computed(() => state.value * 2)
return doubler
}
const doubler = createDoubler(4)
doubler.value // 4
doubler.value = 5
doubler.double // 10
EDIT: After giving it another thought I came up with the following solution:
class Doubler {
constructor(value) {
this._state = reactive({ value });
}
get value() {
return this._state.value;
}
set value(value) {
return this._state.value = value;
}
get double() {
return this._state.value * 2;
}
}
If you want to use ref instead of reactive:
class Doubler {
constructor(value) {
this._value = ref(value);
}
get value() {
return unref(this._value);
}
set value(value) {
return this._value = value;
}
get double() {
return this.value * 2;
}
}
Link to CodeSandbox

Related

Angular 7 application, I want to do an if() condition that checks the Input() value passed from parent

I am passing data from parent to child. In the HTML, i can see the value of the Input() variable. However, on my TS file, when I try to do a conditional to check the value of Input() it is always an empty string. Here is my code for the child:
#Input() checkDbStatus = '';
ngOnInit() {
this.initForm();
this.dbStatusCheck();
}
// disables all controls in a form group
disableControl(group: FormGroup){
Object.keys(group.controls).forEach((key: string) => {
const abstractControl = group.get(key);
abstractControl.disable();
})
}
// disable form controls if dbStatus !== update
dbStatusCheck() {
if(this.checkDbStatus !== 'update') {
this.disableControl(this.demographicsSectionOne);
this.disableControl(this.demographicsSectionTwo);
this.disableControl(this.demographicsSectionThree);
this.disableControl(this.demographicsSectionFour);
this.disableControl(this.demographicsSectionFive);
}
}
I think you need to use the ngChange lifecycle.
https://angular.io/api/core/OnChanges
export class YourComponent implements OnChanges
ngOnChanges(changes: SimpleChanges) {
if (changes.checkDbStatus.currentValue !== changes.checkDbStatus.previousValue) {
this.doStatusCheck();
}
}
Try set and get input() function
https://angular.io/guide/component-interaction
_checkDbStatus: any;
#Input() set checkDbStatus(data: any) {
this._checkDbStatus = data;
this.dbStatusCheck(data)
}
get checkDbStatus(){return this._checkDbStatus }

Vue computed property: helper function returns undefined despite being defined

I am using a computed property diameter() to return either:
- a random number (randomise: true)
- a number returned from an object within an array (randomise: false).
I do have a working implementation (see bottom of post) but would like to know why the cleaner implementation doesn't work. With randomise: false, diameter() returns undefined. Why?
vars [
{varName: diameter, varValue: 25.8},
{varName: quantity, varValue: 68}
]
computed: {
diameter() {
if (randomise) {
return math.randomInt(100, 1000) //no problems
} else {
console.log(this.populateValue('diameter')) //undefined
return this.populateValue('diameter')
}
}
}
methods: {
populateValue(variableName) {
this.vars.forEach(element => {
if (element.varName === variableName) {
console.log(element.varValue) //25.8
return element.varValue
}
})
}
}
The following implementation works but why do I have to create an arbitrary property to do so?
diameter() {
if (!this.vars || !this.passVars) {
return math.randomInt(100, 1000) / (10 ** math.randomInt(0, 3))
} else {
this.populateValue('diameter')
return this.blah
}
}
populateValue(variableName) {
this.vars.forEach(element => {
if (element.varName === variableName) {
this.blah = element.varValue
}
})
}
The problem is that return element.varValue is returning from the forEach, not populateValue.
There are various ways to write this. e.g.
for (const element of this.vars) {
if (element.varName === variableName) {
return element.varValue
}
}
By using a for/of loop there is no inner function so the return returns from the function you're expecting.
Alternatives include:
let value = null
this.vars.forEach(element =>
if (element.varName === variableName) {
value = element.varValue
}
})
return value
or:
const match = this.vars.find(element =>
return element.varName === variableName
})
if (match) {
return match.varValue
}

Use props inside method in vue.js

I have props like below
props: ['applicants',],
I would like to use props like below
methods : {
formated (item) {
var _self = this;
_self.values.length = 0;
if(item == "") {
console.log(this.applicants); //I am getting output here
_self.values = this.applicants
}
else {
console.log(this.applicants); //I am not getting output here
this.applicants.filter(applicant => { })
}
}
}
Why it is happening like this?
You don't need to create a reference to this. Assuming this.values is an array:
methods: {
formatted (item) {
this.values = item == "" ? this.applicants : this.applicants.filter(applicant => { })
}
}

Access an element's Binding

I have a custom attribute that processes authentication data and does some fun stuff based on the instructions.
<div auth="disabled: abc; show: xyz; highlight: 123">
There's a lot of complicated, delicate stuff happening in here and it makes sense to keep it separate from semantic bindings like disabled.bind. However, some elements will have application-logic level bindings as well.
<div auth="disabled.bind: canEdit" disabled.bind="!editing">
Under the covers, my auth attribute looks at the logged in user, determines if the user has the correct permissions, and takes the correct action based on the result.
disabledChanged(value) {
const isDisabled = this.checkPermissions(value);
if (isDisabled) {
this.element.disabled = true;
}
}
This result needs to override other bindings, which may or may not exist. Ideally, I'd like to look for an existing Binding and override it ala binding behaviors.
constructor(element) {
const bindings = this.getBindings(element); // What is the getBindings() function?
const method = bindings['disabled']
if (method) {
bindings['disabled'] = () => this.checkPermission(this.value) && method();
}
}
The question is what is this getBindings(element) function? How can I access arbitrary bindings on an element?
Edit: Gist here: https://gist.run/?id=4f2879410506c7da3b9354af3bcf2fa1
The disabled attribute is just an element attribute, so you can simply use the built in APIs to do this. Check out a runnable example here: https://gist.run/?id=b7fef34ea5871dcf1a23bae4afaa9dde
Using setAttribute and removeAttribute (since the disabled attribute does not really have a value, its mere existence causes the element to be disabled), is all that needs to happen:
import {inject} from 'aurelia-framework';
#inject(Element)
export class AuthCustomAttribute {
constructor(element) {
this.el = element;
}
attached() {
let val = false;
setInterval(() => {
if(this.val) {
this.el.setAttribute('disabled', 'disabled');
} else {
this.el.removeAttribute('disabled');
}
this.val = !this.val;
}, 1000);
}
}
NEW RESPONSE BELOW
You need to work directly with the binding engine. A runnable gist is located here: https://gist.run/?id=b7fef34ea5871dcf1a23bae4afaa9dde
Basically, you need to get the original binding expression, cache it, and then replace it (if auth === false) with a binding expression of true. Then you need to unbind and rebind the binding expression:
import {inject} from 'aurelia-framework';
import {Parser} from 'aurelia-binding';
#inject(Element, Parser)
export class AuthCustomAttribute {
constructor(element, parser) {
this.el = element;
this.parser = parser;
}
created(owningView) {
this.disabledBinding = owningView.bindings.find( b => b.target === this.el && b.targetProperty === 'disabled');
if( this.disabledBinding ) {
this.disabledBinding.originalSourceExpression = this.disabledBinding.sourceExpression;
// this expression will always evaluate to true
this.expression = this.parser.parse('true');
}
}
bind() {
// for some reason if I don't do this, then valueChanged is getting called before created
this.valueChanged();
}
unbind() {
if(this.disabledBinding) {
this.disabledBinding.sourceExpression = this.disabledBinding.originalSourceExpression;
this.disabledBinding.originalSourceExpression = null;
this.rebind();
this.disabledBinding = null;
}
}
valueChanged() {
if(this.disabledBinding ) {
if( this.value === true ) {
this.disabledBinding.sourceExpression = this.disabledBinding.originalSourceExpression;
} else {
this.disabledBinding.sourceExpression = this.expression;
}
this.rebind();
} else {
if( this.value === true ) {
this.el.removeAttribute('disabled');
} else {
this.el.setAttribute('disabled', 'disabled');
}
}
}
rebind() {
const source = this.disabledBinding.source;
this.disabledBinding.unbind();
this.disabledBinding.bind(source);
}
}
It is important that the attribute clean up after itself, as I do in the unbind callback. I'll be honest that I'm not sure that the call to rebind is actually necessary in the unbind, but it's there for completeness.

Is there any kind of wildcard operator in the Durandal observable plugin, to subscribe to a change in any property?

Is there any kind of wildcard operator in the Durandal observable plugin, as there is in (for example) JsObservable?
The Durandal observable documentation gives this example:
var observable = require('plugins/observable');
var viewModel:{
firstName:'',
lastName:''
};
observable(viewModel, 'firstName').subscribe(function(value){
console.log('First name changed.');
});
viewModel.firstName = 'Test';
What I'd like to do is use a wildcard to subscribe to any changed property on the target. Something like this:
observable(viewModel, '*').subscribe(function(property, value){
console.log(property + ' changed.');
});
I don't see anything in the API documentation, but wondered if there was anything undocumented, or if anyone has a workaround to implement this behaviour.
Unfortunately, there is no wildcard operator for this functionality.
But you can easily create wrapper module for this functionality.
Here is small example:
var observable = require('plugins/observable');
var wildcardObservable = function(obj, changeCallback){
for(var prop in obj){
observable(obj, prop).subscribe(changeCallback);
}
}
var changeCallback = function() {
console.log('property changed.');
}
Usage:
var viewModel:{
firstName:'',
lastName:''
};
wildcardObservable(viewModel, changeCallback);
With thanks to U10 for the start above, (and with reference to a few examples on the web) I came up with the following, which uses a closure to track all the necessary properties. It's a bit messy but it does what I need for now - hopefully it will be of use to someone.
var ChangeTracker = (function () {
function ChangeTracker() {
}
ChangeTracker.prototype._trackChange = function (prop, target) {
var type = typeof (target[prop]);
var value = target[prop];
_logger.log("_trackChange", { target: target, prop: prop, type: type, value: value }, "CT");
_obs(target, prop).subscribe(function (newValue) {
var obj = {
target: target,
prop: prop,
newValue: newValue,
oldValue: value
};
_logger.log(">>>>>>>>>>>>>>> CHANGE!", obj, "CT");
value = newValue;
});
};
ChangeTracker.prototype.TrackChanges = function (target) {
var _this = this;
for (var prop in target) {
if (target.hasOwnProperty(prop)) {
this._trackChange(prop, target);
}
var underlying = ko.utils.unwrapObservable(target[prop]);
if (underlying instanceof Array) {
ko.utils.arrayForEach(underlying, function (item) {
_this.TrackChanges(item);
});
} else if (typeof underlying === "object") {
this.TrackChanges(underlying);
}
}
}
};
return ChangeTracker;
})();