i want to filter out the prop in computed, the prop value is available, but computed always shows undefined. Following is my code :
export default {
name: "Validation",
props: {
validationResult: {
type: Object,
required: true,
},
},
computed: {
filteredInvalidRules() {
return this.validationResult.sss.rules.filter((rule) => rule.isValid === false);
},
},
'Validation Results' is aavailable , and has value in dev Tools.
The computed property 'filteredInvalidRules' is always undefined. How to fix it?
TIA
I guess it's undefined because of the component creating steps.
I think it goes something like this (please correct me if I'm wrong):
the child component is created.
your computed property looks after validationResult.
validationResult is required, but has no value yet.
filteredInvalidRules returns undefined
your parent component is created and you pass the data (and you see them in your devTools).
Solution 1: add default values to your props
props: {
validationResult: {
type: Object,
default() {
return {
sss: {
rules: {
isValid: false
}
}
};
},
required: true,
},
},
so when your child component is created, your computed property got access to the default property values.
Solution 2: return in computed property
filteredInvalidRules() {
const isUndefined = obj2?.sss?.rules === undefined
if (isUndefined) return []
return this.validationResult.sss.rules.filter((rule) => rule.isValid === false);
},
this approach will return an empty array on creating the child component. I think it's better to use default values, to keep the computed property clean and to prevent the isUndefinedcheck each time a value changes.
There could be more solutions to it.
Related
My ExtJS application displays certain UI elements depending on a boolean variable.
This boolean variable, however, is the result of calling an async function. As a result, the boolean is set to a Promise that is fulfilled, rather than true or false proper. This affects whether the UI elements are actually displayed (a Promise is not exactly a boolean, after all).
The code looks like this:
Ext.define('userDefinedComponent', {
extend: 'Ext.Container',
requires: ['someHelperFile'],
initComponent: function () {
var me = this,
var enabled = someHelperFile.someAsyncFunc() // enabled is a boolean that is returned as fulfilled Promise instead
Ext.apply(me, {
// layout and padding
items: [
{
xtype: 'internallyDefinedForm',
fieldConfigs: {
// other fields
'someFormField': {
hidden: !enabled, // depends on enabled
}
}
},
{
xtype: 'internallyDefinedGrid',
columnConfigs: {
// other columns
'someColumn': {
hidden: !enabled, // deends on enabled
}
},
}
]
})
}
})
I want the field enabled to really be a boolean rather than a Promise. In other words, I want to wait for the someAsyncFunc to run the result, before setting hidden property of the internallyDefinedForm and internallyDefinedGrid.
What are my possibilities? I was thinking of using a beforerender, like below:
Ext.define('userDefinedComponent', {
extend: 'Ext.Container',
requires: ['someHelperFile'],
initComponent: function () {
var me = this;
Ext.apply(me, {
// layout and padding
items: [
{
xtype: 'internallyDefinedForm',
fieldConfigs: {
// other fields
'someFormField': {
hidden: !me.enabled, // depends on enabled
}
}
},
{
xtype: 'internallyDefinedGrid',
columnConfigs: {
// other columns
'someColumn': {
hidden: !me.enabled, // deends on enabled
}
},
listeners: {
beforerender: function() { // this is the beforerender
me.enabled = someHelperFile.someAsyncFunc();
console.log("beforerender triggered in grid");
}
},
}
]
})
}
})
And in fact, using the beforerender for the internallyDefinedGrid only, I can see the text "beforerender triggered in grid" triggered very early. However, the fact remains that the behavior that I observe does not correspond to what I expect: although the async someAsyncFunc should return true based on the API response it gets, such that me.enabled is true, the actual UI associated with the internallyDefinedGrid behaves as if me.enabled is false instead. I observe that the column on the UI is hidden, and this is only possible when me.enabled is false, such that the column someColumn does not appear on the grid. After all, the hidden field of someColumn is set to !enabled.
I am confident that the UI for the grid behaves not like what I expect it to, so there is a problem with the async behavior. But I'm really lost as to how to set the asynchronously obtained enabled or me.enabled field adequately.
Any help is appreciated.
I would suggest to use a View Model and binding, as explained here.
Basically you define what your UI is depending on, under the data tag in the View Model (you can set the initial value here):
Ext.define('MyApp.TestViewModel', {
extend: 'Ext.app.ViewModel',
data: {
something: false,
},
}
Then you bind the visibility to this value in the view:
bind: {
hidden: '{something}'
}
or
bind: {
hidden: '{!something}'
}
You fetch the async data, and once you have the result, set the value in the View Model (this can be either the view or the controller):
this.getViewModel().set('something', RESULT_OF_ASYNC)
With binding ExtJS takes care of refreshing the visibility of your component every time when the value in the View Model is changed. There are good examples at the link I provided. This is a very powerful and complex feature of ExtJS, worth learning.
Is there a way to detect change to modelValue in a custom component? I want to push the change to a wysiwyg editor.
I tried watching modelValue but emitting update for modelValue triggered that watch, which created circular data flow.
Code:
export default {
props: ['modelValue'],
watch: {
modelValue (val) {
this.editor.editor.loadHTML(val)
}
},
mounted () {
this.editor.editor.loadHTML(val)
this.editor.addEventListener('trix-change',
(event) => this.$emit('update:modelValue', event.target.value))
}
}
<TextEditor v-model="someHtml"></TextEditor>
In VueJS v3, the event name for custom v-model handling changed to 'update:modelValue'.
You can listen to these events like this: v-on:update:modelValue="handler"
For a more complete example, lets assume you have a Toggle component with these properties/methods:
...
props: {
modelValue: Boolean,
},
data() {
return {
toggleState: false,
};
},
methods: {
toggle() {
this.toggleState = !this.toggleState;
this.$emit('update:modelValue', this.toggleState);
}
}
...
You can use that Toggle component:
<Toggle v-model="someProperty" v-on:update:modelValue="myMethodForTheEvent"/>
As a side note, you could also v-model on a computed property with a setter; allowing you to internalise your state changes without using the update:modelValue event. In this example, it assumes you v-model="customProperty" on your custom Toggle component.
computed: {
customProperty: {
get() {
return this.internalProperty;
},
set(v) {
this.internalProperty = v;
console.log("This runs when the custom component 'updates' the v-model value.");
},
}
},
I had the same problem and solved it using a slight tweak to the way you call the watch function:
setup(props) {
watch(() => props.modelValue, (newValue) => {
// do something
})
}
Hence, the important thing is to add () => props.modelValue instead of just putting props.modelValue as the first argument of the watch function.
try that:
watch: {
...
modelValue: function(val) {
console.log('!!! model value changed ', val);
},
...
I have made this simple property (Polymer 2.x):
static get properties() {
return {
bpm: {
type: Number,
value: () => {
return 0
},
observer: "_bpm"
}
}
}
I tried to update it using this.bpm = 60; in a function called when clicking a button. If I output the value using console.log(this.bpm); it displays the correct value, but my heading <h2 id="bpm">[[bpm]]</h2> is not updated and the observer is not called.
When bpm is set using something like <paper-slider value="{{bpm}}"></paper-slider> it works.
What am I doing wrong? Thank you for your help!
It will be easier for the community to know that this question was answered into the comments of the requests.
Initial problem : Binding value not updated because bpm property was set from a function outside of the element.
Correction : Here a working JSFiddle (to use in chrome) used to demonstrate how to use the binding.
I also faced similar issue due to setting the property from a different function. Putting it here for reference.
My code:
Polymer({
is: 'test-test',
properties: {
min: {
type: Number,
value: -1,
observer: '_minChangedd'
}
},
_minChangedd: function (val) {
console.log(val);
},
ready: function () {
setInterval(function () {
this.min = this.min + 1;
}, 500);
},
});
Problem:
The setInterval function had its own this and so the expression this.min actually refers to min of setInterval.
Using arrow functions resolved the issue, by replacing the call with setInterval(() => {...});
I am using VuexFire in an app and as in https://github.com/posva/vuexfire in my components I have lines like
computed: Vuex.mapGetters(['todos']),
created() {
this.$store.dispatch('setTodosRef', todosRef)
},
This works fine except when I also have some computed methods in which case I don't know what to do. I have tried things like:
computed: Vuex.mapGetters(['todos']),
computed() {
amethod: { return 1 }
},
created() {
this.$store.dispatch('setTodosRef', todosRef)
},
and
computed() {
amethod: { return 1 }
},
created() {
...Vuex.mapGetters(['todos'])
this.$store.dispatch('setTodosRef', todosRef)
},
but these, and the other things that I have tried, are clearly misguided because they don't work (i.e. the data from firebase is not available to the method.
What's the correct approach?
First of all, you will always specify your computed properties as an object, not as a method (as you're doing with computed()).
Secondly, you need to use the spread operator in the computed object:
computed: {
amethod() { return 1 },
...Vuex.mapGetters(['todos'])
}
This effectively spreads out all of the computed property methods returned by Vuex.mapGetters and defines them as properties of the computed object.
I'm trying to use a computed property based on the values from an async, hasMany model property, but cannot get it to display in my view.
MyApp.Foo = DS.Model.extend({
title: DS.attr('string'),
peeps: DS.hasMany('peep', { async: true });
});
MyApp.Peep = DS.Model.extend({
name: DS.attr('string'),
email: DS.attr('string')
});
MyApp.Foo.FIXTURES = [
{ id: 1, title: 'nice', peeps: [1,2] }
];
MyApp.Peep.FIXTURES = [
{ id: 1, name: 'mypeep', email: 'peep#example.com' },
{ id: 2, name: 'mypeep2', email: 'peep2#example.com' }
];
MyApp.FooController = EmberObjectController.extend({
showPeeps: function() {
// This one works for this test data.
// return [{name: 'baz', email: 'bar'}];
var peepList = this.get('content.peeps.[]').then(function(c) {
// This one does not work, even for this test data.
return {name: 'baz', email: 'bar'}];
});
}.property('content.peeps.[]');
});
In my view, something along the lines of:
{#each peep in controller.showPeeps}}{{peep.name}}{{/each}}
I can see all the data in the "then()" using console.log(), and as it indicates in the code comments, it works if I take the return out of the "then()" - but then the real data is empty because it is returned as async. If I try to make it non-async, I get
Uncaught TypeError: Cannot call method 'resolve' of undefined
I've tried many variants of the computed property code (using #each, using model.peeps - all of which correctly show the data in console.log(), but not in the view. In the view, it is always undefined unless I just return dummy data outside of the then() - which displays correctly)
What am I missing?
Don't treat the hasMany relationship as a promise, treat it as an array. That's the whole point of DS.PromiseArray. If you just want the users, don't even bother with the computed property, just use peeps in your template. But, if you need to convert the data somehow, use map.
showPeeps: function() {
return this.get('peeps').map(function(peep) {
return { name: peep.name, email: peep.email };
});
}.property('peeps.#each')
Also, don't watch the [] property. That only updates when an item is added or removed from the array. Your array contents aren't changing, the contents of the contents are changing. You should watch the #each property instead. You also don't need to add [] to the end of the property name, and you don't need to prefix the property with content..