Why the following codes are not equivalent?
With computed:
computed: Ember.computed('selected', function() {
console.log('computed');
return this.get('selected');
}),
observer1: Ember.observer('computed', function() {
console.log('observer1');
}),
observer2: Ember.observer('selected', function() {
console.log('observer2');
}),
With alias:
computed: Ember.computed.alias('selected'),
observer1: Ember.observer('computed', function() {
console.log('observer1');
}),
observer2: Ember.observer('selected', function() {
console.log('observer2');
}),
The first one only prints observer2 and the second one prints observer1 and observer2.
In the first one, computed is just a getter. So if you don't use computed in template or other places to react ASAP on changing, it's not called by changing selected.
But in second one, alias creates setter too. So if you change selected, computed changes after that quickly.
If you use computed in first one in a template, the same result comes.
Related
I am trying to move some functionality to a vue mixin from the component, to be able to use it in multiple components.
This (simplified version of the code) works:
export default {
data() {
return {
file: {},
audioPlayer: {
sourceFile: null,
},
};
},
watch: {
'audioPlayer.SourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
}
But if I move the audioPlayer data object to a mixin, the watch does no longer fire.
Is this expected behavior?
N.b. I resolved this by directly making the 'file' data property into a computed value, which works in this particular case, but the behavior is still strange.
You need a lowercase s. sourceFile not SourceFile
watch: {
'audioPlayer.sourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
I do not understand why my new code does not work. I was able to extract a minimum reproducible case. When the created() sets a data synchronously, it works well and an article radio is displayed. When I surround it with timeout, then the blog stays selected. Vue 2.6.12
The bug in this code has been fixed, but this was not the cause for my troubles because my real code is different. My problem is that the radio button is not checked when it should be after the reactive data is changed.
<Radio
v-model="type"
identifier="article"
class="pl-3"
label="article"
name="type"
/>
<Radio
v-model="type"
identifier="blog"
class="pl-3"
label="blog"
name="type"
/>
<div>Selected {{ type }}</div>
data() {
return {
type: "blog",
};
},
created() {
setTimeout(function () {
this.type = "article";
console.log(this.type);
}, 800);
},
This makes my head explode because a similar code in different component works well.
UPDATE:
my original code, that does not work, is
computed: {
blog() {
return this.$store.getters.BLOG;
},
},
watch: {
blog() {
this.type = (this.blog.info.editorial) ? 'article' : 'blog';
},
created() {
this.$store.dispatch('FETCH_BLOG', { slug: this.slug });
},
Relevant source code:
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/views/item/WriteBlog.vue
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/components/atoms/Radio.vue
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/modules/vuex/items.js
All you need is to change your function to an arrow function because it isn't point your data like this
setTimeout(() => {
this.type = "article";
console.log(this.type);
}, 800);
The problem is the selected property in Radio.vue is only set equal to value in the created() hook. When the setTimeout() occurs in the parent component, Radio.vue's v-model property is changed, which updates its value property, but its selected property is not automatically updated to match.
The solution is to replace the created() hook change with a watcher on value that updates selected:
// Radio.vue
export default {
created() {
// ⛔️ Remove this
//if (this.value) {
// this.selected = this.value
//}
},
watch: {
value: {
handler(value) {
this.selected = value
},
immediate: true,
},
},
}
demo
I assume your original code does not set the type in vue's data function, so it will not reactive when you assign this.type to a new value.
Manage state in a form is complicated, check out this library: https://github.com/vue-formily/formily and maybe it helps you easier to work with form, it will let you separate the form definition from vue component that makes it reusable, and it will manage the state for you...
Here is a small demo for your problem: https://codepen.io/hqnan/pen/YzQbxxo
I got a codesandbox that reproduces my problem: codesandbox example watcher not triggering.
I am working on a component that relies on an object with data that can be dynamically added to the object, so for example, in a seperate .js file, I am exporting the following object:
export default {
defaultSection1: {
displayName: 'Action',
},
defaultSection2: {
displayName: 'Thriller',
},
}
I import this object in my component.
I got a debouncer setup from Lodash so that when data changes, it only fires the watcher once after two seconds. The watcher IS triggered perfectly fine for the data that is already in the object (type in the input text box in my example, and the watcher is triggered). But when I dynamically add data to the object, the watcher is not triggered at all. Only when I change routes back and forth, the data is updated, but the watcher is not triggered. Why is this? What can I do so that the watcher is triggered when data is dynamically being added to an object.
methods: {
fetchAndUpdateData(){
console.log('Fetching data...')
},
addCustomSection(){
//The watcher should be triggered when this function is called
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}
}
},
computed: {
dataWatcher() {
return this.data;
},
updateData() {
return debounce(this.fetchAndUpdateData, 2000);
},
},
watch: {
dataWatcher: {
handler() {
console.log('Watcher triggered')
this.updateData();
},
deep: true,
},
},
Why is the watcher not triggered, when clearly the data is being changed?
Another thing I noticed that is quite strange, is that in Vue 3.0, the watcher IS triggered with exactly the same code.
Codesandbox in Vue 2.6.11, watcher not triggering.
Codesandbox in Vue 3.0, watcher IS triggered with exactly the same code.
In vue 2 there's reactivity issue when updating an item in an array or a nested field in an object, to solve this you've to use this.$set() method :
this.$set(this.data,newSectionId, {displayName: 'Custom'})
this issue is solved in Vue 3. and you could just do :
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}
I am using the mapGetters helper from VueX but i have some problem only on the first load of the page, it's not reactive...
Let me show you :
my html template triggering the change :
<input type="number" value="this.inputValue" #change="this.$store.dispatch('setInputValue', $event.target.value)">
my store receiving the value
{
state: {
appValues: {
inputValue: null
},
},
getters: {
getInputValue: (state) => {
return state.appValues.inputValue;
},
},
mutations: {
setInputValue(state, value) {
state.appValues.inputValue = value;
},
},
actions: {
setInputValue(context, payload) {
return new Promise((resolve, reject) => {
context.commit('setInputValue', payload);
resolve();
});
},
}
}
and then my component listening the store :
import {mapGetters} from 'vuex';
computed: {
...mapGetters({
inputValue: 'getInputValue',
}),
}
watch: {
inputValue: {
deep: true,
immediate: true,
handler(nVal, oVal) {
console.log("inputValue", nVal, oVal);
}
},
}
So now, when i first load the page I get this console.log "inputValue" null undefined which is totally normal because as I have nothing in my store it gaves me the default value null.
But now it's the weird part. I start changing the input value and I don't have nothing appearing in my console. Nothing is moving...
Then I reload the page and on the load I get this console.log "inputValue" 5 undefined (5 is the value I entered previously) so as you can see, when I was changing the input previously, it was well keeping the value in the store but the computed value was not updating itself...
Ans now, when I change the value of the input I have my console log like this "inputValue" 7 5 so it's working as I would like it to work from the start...
What do I do wrong? Why on the first load the computed value not reactive?
Thanks for your answers...
I think the best way to solve this issue is to store a local variable with a watcher, and then update vuex when the local is changed:
On your component:
<input type="number" v-model="value">
data() {
return {
value: ''
};
},
computed: {
...mapGetters({
inputValue: 'getInputValue'
})
}
watch: {
value(value){
this.$store.dispatch('setInputValue', value);
},
inputValue(value) {
console.log('inputValue', value);
}
},
created() {
// set the initial value to be the same as the one in vuex
this.value = this.inputValue;
}
Please take a look at this sample: https://codesandbox.io/s/vuex-store-ne3ol
Your mistake is, you are using this keyword in template. One shouldn't use this in template code.
<input
type="number"
value="inputValue"
#change="$store.dispatch('setInputValue', $event.target.value)"
>
Bonus tip: It is redundant to use a getter to return the default state
if you can just use mapState to return the state.
There are a few small mistakes in the template. It should be this:
<input type="number" :value="inputValue" #change="$store.dispatch('setInputValue', $event.target.value)">
I've removed the this. in a couple of places and put a : out the front of value. Once I make these changes everything works as expected. The this.$store was causing console errors for me using Vue 2.6.10.
I would add that you're using the change event. This is the standard DOM change event and it won't fire until the field blurs. So if you just start typing you won't see anything happen in the console. You'd be better off using input if you want it to update on every keystroke. Alternatively you could use v-model with a getter and setter (see https://vuex.vuejs.org/guide/forms.html#two-way-computed-property).
My suspicion is that when you were reloading the page that was triggering the change event because it blurred the field.
Ok, so ... I found the problem and it was not relative to my examples, I can't really explain why, but I'll try to explain how :
In my store I have the next method :
mutations: {
deleteAppValues(state) {
state.appValues = null;
}
}
I was using this one on the Logout, or when the user first comes on the pageand was not logged-in... So what was going-on?
The User first load the page, the store is initializing well, and the index inputValue is initialized with null value, so it exists...
... But as the User is not logged, I destroy the store so now the inputValue is not equals to null, it just doesn't exist...
Trying to use mapGetters on something that don't exists, the reactivity won't work, so if I dispatch a change, the store key will be created, but as the mapGetters was initialized with an inexisting key, it doesn't listen the reactivity...
After reloading the page, the key now exists in the store so the getter can be attached to it and so now everything working fine...
This is exactly the explaination of what was going wrong about my code... So to make it works fine, I just changed my destruction mutation to :
mutations: {
deleteAppValues(state) {
state.appValues = {
inputValue: null,
};
}
}
Like this, the inputValue key of the store object will always exists and so the getter won't lose his reactivity...
I tryed to make a simple concise question but that made me forgot the bad part of my code, sorry.
I use Ractive to display data from a constantly changing array (received from PouchDB) with the magic: trueparameter. That worked fine until in Version 0.9.x this parameter was no longer available.
So it is advised to use ractive.update()every time my array changes. The problem I'm facing seems quite simple, but I'm fairly inexperienced:
For tracking the changes I do roughly the following (taken from a PouchDB script):
fetchInitialDocs().then(renderDocsSomehow).then(reactToChanges).catch(console.log.bind(console));
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
//magic: true,
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues}
});
}
function reactToChanges() {
//here my changes appear
onUpdatedOrInserted(change.doc);
myRactive.update() // <- I cannot use update here because "myRactive" is not known at this point.
}
What I tried also was setting an observer in the renderDocsSomehow() function
ractive.observe('doc1', function(newValue, oldValue, keypath) {
ractive.update()
});
but this did not do the trick.
So how can I inform Ractive that my data has changed and it needs to update itself?
If reactToChanges is just a function that sets up "listeners" for myRactive and is never called elsewhere, you can probably put the code for it in an oninit. This way, you have access to the instance.
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
//magic: true,
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues},
oninit(){
//here my changes appear
onUpdatedOrInserted(change.doc);
this.update()
}
});
}
I found some sort of answer and thought I'd share it, even if it is not working 100% as desired.
If I return ractive from my first function It's usable in the second one:
fetchInitialDocs().then(renderDocsSomehow).then(reactToChanges).catch(console.log.bind(console));
function renderDocsSomehow() {
let myRactive = new Ractive({
el: '#myDiv',
template: 'my Template',
data:{
doc1: docs
},
components: {myComponents},
computed: {computedValues}
});
return myRactive;
}
function reactToChanges() {
//here my changes appear
onUpdatedOrInserted(change.doc);
renderDocsSomehow().update()
}
Sadly update()changes the whole ractive part of my page to its default state (every accordion that was folded out before is reverted and every chart.js is redrawn), which of course is not desirable in a highly dynamic application. Therefore I will nor mark it as answer by now.