In vue.js is it possible to notify "observers" to refetch the value from the observed data, without changing the value of the observed data - vue.js

Suppose that I have an input element bound like this:
<input :value="somedata">
The user types something in the input, and since I am not using v-model or altering somedata through a handler, the value of the element is now different from somedata. This is what I want, but I would also like to have the following capability:
Without changing the value of somedata I would like to be able to notify the element so that it sets its value equal to somedata again. Something like knockout's notifySubscribers() or valueHasMutated()
Is that possible in vue.js?
UPDATE: A clear illustration of the issue here: https://jsfiddle.net/gtezer5c/3/

It's a little difficult interpreting what exactly the requirements and acceptance criteria might be to suit your needs, and I thought Bill's solution was what you were after, but after all the updates and clarifications, I think I understand a little more what you're trying to accomplish: in short, I think you need a generic way to have an input that can hold a value but that can be independently reverted to some other value.
Please have a look at this CodePen. I believe it's providing what you're trying to do. It allows you to create an input element as a revertable component, which can optionally be passed a default value. Any changes to that input are maintained by the component with its value data property. It will not be observing/pulling in any lastKnownGood type of value because any reversion will be pushed into the component from outside.
Externally to the revertable component, you can $emit a revert event with a new value that will cause either all revertable components or a single revertable component (with a matching ID) to be reverted.
I feel like it's mostly a clean solution (assuming I'm understanding the requirements correctly), except that in VueJS 2 we have to use a standalone, shared Vue object to pass the events when there is no parent-child relationship. So you'll see:
const revertBus = new Vue()
defined in global scope in the demo. And the revertable component will use it to receive incoming messages like so:
revertBus.$on('revert', (value, id) => { ... }
and the controlling Vue object that is triggering the messages will use it like this:
revertBus.$emit('revert', this.reversionValue, targetId)
You can also emit the event with a null value to cause the revertable component to reset its value to its initial default value:
revertBus.$emit('revert', null, targetId)
Again, it's a mostly clean solution, and though it might not fit perfectly inline with what you're trying to accomplish, I'm hoping it might at least help you get closer.

I'm not sure I'm following properly but I'll give it a shot.
What I think you want is to only update some values when their "temporary" values meet some type of condition. Here's how I was thinking of it.
<div id="app">
<input v-model="tempValues.one">
<input v-model="tempValues.two">
<input v-model="tempValues.three">
<pre>{{ values }}</pre>
<pre>{{ tempValues }}</pre>
</div>
Then, in my component, I watch tempValues and only update values when a condition is met.
new Vue({
el: '#app',
data: {
values: {
one: '',
two: '',
three: '',
},
tempValues: {},
},
created () {
// Create the tempValues based on the real values...
this.tempValues = Object.assign({}, this.values)
},
methods: {
updateValues (tempValues) {
// Only updating the values if all the tempValues are longer than 3 characters...
var noneEmpty = Object.values(tempValues).every((value) => value.length > 3)
if (noneEmpty) {
this.values = Object.assign({}, tempValues)
}
},
},
watch: {
// Watch tempValues deeply...
tempValues: {
handler (tempValues) {
this.updateValues(tempValues)
},
deep: true,
},
},
})
Here's a quick demo: https://jsfiddle.net/crswll/ja50tenf/

yourvar.__ob__.dep.notify()
works on objects and arrays

Yes, You should be able to do this with help of v-on:input. You can call a function on input and put your logic of checking and updating in this function:
<input :value="somedata" v-on:input="yourMethod">
In fact if you look at the documentation, <input v-model="something"> is syntactic sugar on:
<input v-bind:value="something" v-on:input="something = $event.target.value">
so instead of assigning variable something to value inputted, you can put your logic in that place.

Related

Vue v-model issue when using a computed setter

I want to create an input field that the user can fill out. The catch is that I don't want them to fill this field in with special characters. Currently, I have this html setup:
<input class="rc-input-editing" id="bioInput" type="text" v-model="wrappedBioName">
And this Vue.js setup (As you can see, I'm trying to approach this problem using a computed setter) :
data () {
return {
newBioName: '',
}
},
computed: {
wrappedBioName: {
get () {
alert('getting new name!')
return this.newBioName
},
set: function (newValue) {
const restrictedChars = new RegExp('[.*\\W.*]')
if (!restrictedChars.test(newValue)) {
this.newBioName = newValue
}
}
}
Currently, my issue is that the client is able to continue filling out the text input field, even when this.newBioName isn't updating. In other words, they are able to enter special characters into the input field, even though the this.newBioName isn't being updated with these special characters.
This behavior is different than what I'm expecting, given my current understanding of v-model. Based on what I've read until now, v-model binds the input element to some vue instance data, and that vue instance data to the input element (two way binding). Therefore, I'm expecting that the text in the text input field will directly match the value of this.newBioName.
Clearly, I'm missing something, and would appreciate a second pair of eyes!
Vue.js two way binding system doesn't work as you expected. Each binding process works one way each time. So, the thing you should do is not to let the input text change.
Try keypress event instead of computed property like this:
<input class="rc-input-editing" id="bioInput" type="text" v-model="newBioName" #keypress="nameKeyPressAction">
data() {
return {
newBioName: ""
};
},
methods: {
nameKeyPressAction(event) {
const restrictedChars = new RegExp("[.*\\W.*]");
const newValue = this.newBioName + event.key;
if (!restrictedChars.test(newValue))
this.newBioName = newValue;
return event.preventDefault();
}
}
Edit:
When you set a data property or a computed property as v-model of an input, vue associates them and yet, if user updates dom object via the input, property's setter is triggered and the process ends here. On the other hand, when you change the value of the property on javascript side, vue updates the dom object and this process also ends here.
In your sample code, it seems like you expect that the computed property's getter to set the dom again but it can't. The property is already updated via dom change and it can't also update it. Othervise, there might occur infinite loop.

Vue - same mutation refreshes (or not!) components depending on which component it is called from?

I have problem understanding why THE SAME mutation fails to refresh data displayed in components (although it does change underlying vuex store data!) if it is called from one of the components, but it does refresh the data if called from another component?
I am updating Filter objects stored in store this way: state.report.filters[], where filters is array of Filter objects.
const state = {
report: {
filters: [], // array of Filter objects
...
}
}
My mutation looks for a filter in the array and substitutes the whole Filter object.
const mutations = {
setFilter: (state, newFilterValue) => {
let changedFilter = state.report.filters.find(filter => {
return filter.fieldName === newFilterValue.fieldName;
});
changedFilter = newFilterValue;
}
}
The mutation is called from a method of Filter class defined like this (separate module):
import { store } from './store';
export class Filter {
constructor ({
...
} = {}) {
this.operators = []; // Array of Operator objects
this.value = []; // Array of values - in this case Dates
};
updateOperator (operatorName) { // this mutation refreshes components when executed
this.operator[0] = new Operator(operatorName);
store.commit('setFilter', this); // whole object passed to the mutation
};
updateValue (newValue) { // this mutation changes store value, but fails to refresh components
this.value[0] = newValue; // newValue is a Date
store.commit('setFilter', this);
};
};
The app displays data in rows (each Filter has a separate row), each row contains cells, of which one contains components dedicated to Filter's value and Operator. These dedicated components receive as props callback functions which are methods of the Filter object. They execute the callback functions when a new value is entered passing the value to the Filter which then updates a relevant property and calls the mutation passing in both cases the whole Filter object as payload.
// TABLE CELL COMPONENT displaying filter value and operator
<template>
<td>
<operator-component
:iconName="proppedFilterObject.operator.iconName"
:callback="proppedFilterObject.updateOperator.bind(proppedFilterObject)"
></operator-component>
<value-component
:date="proppedFilterObject.value[0]"
:callback="proppedFilterObject.updateValue.bind(proppedFilterObject)"
></value-component>
</td>
</template>
<script>
export default {
props: ['proppedFilterObject'] // whole filter object
};
</script>
// OPERATOR COMPONENT
<template>
<div #click.stop="chooseOperator">
{{ iconName }} // some operator value display
</div>
</template>
<script>
export default {
methods: {
chooseOperator () {
const modal = new ChooseOperatorModal({
callback: this.callback // this displays another modal for receiving data. The modal calls the callback.
});
},
},
props: ['callback', 'iconName']
};
</script>
// VALUE COMPONENT
<template>
<date-picker v-model="computedDate"> // THIRD PARTY COMPONENT
</date-picker>
{{ date }} // additional display to verify if there's a problem within 'date-picker'
</template>
<script>
import DatePicker from 'vue2-datepicker'; // THIRD PARTY COMPONENT
export default {
components: { DatePicker },
computed: {
computedDate: {
get: function () {
return this.date;
},
set: function (newValue) {
this.callback(newValue);
}
}
},
props: ['callback', 'date']
};
</script>
So, if eg. I enter new operator value from Operator component, everything refreshes. When I enter a new value in the value component, the mutation is executed and store value changed, but displayed data are not refreshed. However, if afterwards I change an operator all the components will refresh and value will get displayed. Even if I change operator in a different Filter object(!). Ie:
a) Change in report.filters[0].value - display not refreshed, but...
b) then change report.filters[1].operator - both report.filters[1].operator AND PREVIOUSLY CHANGED report.filters[0].value get refreshed(?!).
What can be a reason of such behaviour? Where to look for the problem?
Some additional remarks:
1) I am using a third party component "vue2-date-picker" for date choice and display. However it does not seem to be responsible for the problem, as if I try to display the new value just in {{ }} notation it behaves the same way. I have used the date picker in other components and there it functions correctly as well.
2) In the code samples I left out most imports/exports and other seemingly irrelevant elements to keep the question reasonably short.
There are a lot of problems with the code and several of them are contributing to the problems you're seeing. A full, thorough answer that addresses all of these problems would be ridiculously long so instead I will skim through them without going into huge amounts of detail. You will need to do some further reading and experimentation to understand each of these topics properly.
Let's start with this line in the mutation:
changedFilter = newFilterValue;
This line assigns a new value to the local variable changedFilter. That's all. As it's the last line of the mutation the net result is that it doesn't really do anything.
Presumably your intent was to update the array state.report.filters, replacing the old entry with a new entry. However, just updating a local variable isn't going to do that.
At this point you may be wondering 'If that doesn't do anything, then why is the state in my store changing?'. I'll come to that in a moment but first let me prove to you that your existing code does nothing.
Try removing the code inside setFilter completely. Just leave an empty function. Then try clicking around in the UI just like you did before. You'll find that the store state updates just the same as it did before, even though you've removed the code to update the array.
The correct way to implement that mutation would be to use findIndex to find the relevant index and then use either Vue.set or the array's splice method to update the array accordingly. That will change the item in the array. However...
This brings us back to the earlier question. Why is the state updating if the mutation does nothing?
This is because you're using the same object in multiple places. The Filter object held in the array is the same object that your UI is editing. There are no copies being taken, there is just a single object. So when you change the properties of that object inside updateOperator or updateValue this will immediately be reflected inside the store. Calling the setFilter mutation is just asking the store to replace an object with itself.
There's nothing specific to Vue about this. This is just the standard behaviour of reference types in JavaScript. It is also common with many other programming languages that don't directly expose pointers. It can be useful to learn a little about how pointers work in other languages as it will give you a better initial mental model before attempting to understand how reference types behave in JavaScript. Understanding the difference between 'by value' and 'by reference' may also be a useful starting point.
The next topic to cover is reactivity, which very much is a Vue topic.
Specifically, there are certain changes that Vue can't detect. These are usually referred to as the reactivity caveats. You can find more about them in the official documentation:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
https://v2.vuejs.org/v2/guide/list.html#Caveats
There are at least two lines in your code that violate these rules:
this.operator[0] = new Operator(operatorName);
and
this.value[0] = newValue;
You can't set array entries directly by index. The array will update but it won't trigger any reactive dependencies within Vue. Instead you need to use either Vue.set or one of the array methods, e.g. push, pop, splice, etc.. In this example you could use splice.
e.g. Using Vue.set:
Vue.set(this.value, 0, newValue);
e.g. Using splice:
this.value.splice(0, 0, newValue);
Why does all of this matters?
Well Vue will only re-render a component if its reactive dependencies have changed. They are very similar to computed properties in that regard. Here's how it works...
Vue compiles the template down to a function. That function is referred to as the render function. When rendering a component Vue calls the render function and that function returns a description of how to render the component. Any reactive properties that are touched while that function is running will be recorded as dependencies. If, at some point in the future, the value of one of those reactive properties changes then Vue will rerun the render function to generate a new rendering of that component.
There are two key points to take out of this description:
If you fall foul of one of the reactivity caveats then Vue won't know the dependency has changed, so it won't re-render the component.
The render function runs as a whole. It doesn't just target a small chunk of the template, it always runs the whole thing.
So if you change a dependency in a non-reactive way (i.e. one of the caveats) it won't trigger a rendering update. But if you subsequently update a dependency properly, Vue will detect that and will rerun the render function. When it runs it will run the whole thing, so any new values will be picked up, even if they weren't detected when they changed.
It isn't immediately clear to me which rendering dependency is causing your component to re-render. However, it only needs one of them to change in a detectable manner. Any other changes will then get pulled in incidentally when the render function runs and reads their current values.
That covers why your code isn't working. However, I would also worry about your decision to introduce a Filter class. I understand how that may be appealing if you've come from some other OO environment but it isn't typically how Vue is used. It is possible to make it work but you will need a good understanding of both JavaScript reference types and the Vue reactivity system to avoid falling through the cracks. There is no reason why using a specific class to hold your data can't be made to work but in practice it usually ends up being less maintainable than not using such a class. A more typical Vue approach would be to use simple, anonymous objects/arrays to hold the data and then for the data owner (either a component or store module) to be responsible for making any mutations to that data. Events are used to pass changes up the component hierarchy rather than callback props.
Ultimately you will need to judge whether the Filter class is justified but it is probably not what future maintainers of your code will be expecting.

Why is self multiplying returning rediculously large number?

I have following simple html and Vuejs code. When I ran this, I was surprised to find out that the output was displayed as following.
3 times num = 4.6383976865880985e+49
I was expecting to see 30.
If I make a variable and return that temporally variable, I do see the expected value of 30. What is going on here?
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="test">
<p>3 times num = {{ mulBy3() }}</p>
</div>
new Vue({
el: '#test',
data: {
num: 10
},
methods: {
mulBy3: function() {
this.num = this.num * 3;
return this.num;
}
}
});
There's a vital lesson in this! Vue updates your page to reflect changes in your model. If rendering your page causes a change in your model (that causes Vue to re-render the page), then you have created an infinite loop - hence your very big number.
The moral of the story is that you don't know and shouldn't care when or how often your templates are rendered. You create the bindings such that your page reflects your model as you wish, then you leave Vue to take care of it. In practice, never call methods from a render function. Render functions should use data, injects, props, computed, perhaps watch: anything that's reactive. Methods should be used for responding to user activity and processing it back into the model.
You're running in to an infinite loop...
You call mulBy3() in your template
This mutates the num data property
This triggers a redraw
Goto #1
What you should do instead is use a computed property, eg
computed: {
mulBy3 () {
return this.num * 3
}
}
<p>3 times num = {{ mulBy3 }}</p>
This will react to changes to num.
For more reasons why you should not call methods in your templates, see https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods

How to Initialize Data Properties with Prop Values

Still a little bit young in VueJS but I'm loving every bit of it. But now, fixated somewhere.
I want to initialize some values in data() using values passed via props. This is so that I can be able to mutate them later on, since it is not recommended to mutate props inside a component. In fact the official docs recommend this property initialization using prop values as shown below:
{
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
I have something like the one below:
<template>
<div class="well">
<!-- Use Prop value directly on the template: works (but of no help in initializing data) -->
Department: {{department.name}}
<!-- Use prop value but gotten via computed property: Works inside the template but not in the initialization -->
Department: {{fetchDepartment.name}}
<!-- Use the array I initialized with the prop value: Does not work -->
Department: {{this_department.name}}
</div>
</template>
<script>
export default {
name: 'test',
props: ['department'],
data() {
return {
this_department: this.department
// below does not work either
//this_department: this.fetchDepartment
}
},
created() {
// shows empty array
console.log(this.department)
},
mounted() {
// shows empty array
console.log(this.department)
},
computed: {
fetchDepartment() {
return this.department
}
}
}
</script>
As seen in the commented sections above, the initialization is not successful. Neither does the value of this.department appear either from the created() or the mounted() hooks. And note, I can see it is defined using the Chrome Vue Devtools. So my question is, how exactly should I initialize data() attributes using props values, or which is the best way of going around this issue?
I know my answer comes in late but it helps me and hopefully someone else coming here. When props' data are async:
// in the parent component
<template>
<child :foo="bar" v-if="bar" />
</template>
That way, you render the component when props are already available making it safer to follow the guide's recommended ways to initialize data value with props as so:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Happy coding!
You CAN modify a prop. Use the '.sync' modifier. I use it frequently because it is convenient and intuitive. This requires emitting an event to update the value on the parent. I am not really sure the warning of how it results in maintenance issues.
Another method I use if I want to modify a value and not update the parent is using Lodash clone. For example (assuming its available on mounted)
mounted(){
this_department = _.clone(this.department)
}
If you consistently want to mutate the prop and have it change with the parent, then use a computed property. However, in most cases you will want to depend on the state of that data within the component and change it using other functions and thus a computed property will not be what you need.
A computed property is the simplest way to provide a mutable version of a prop, but you might not want to lose data when the prop is updated. You could use an explicit watch.
Watchers
While computed properties are more appropriate in most cases, there
are times when a custom watcher is necessary. That’s why Vue provides
a more generic way to react to data changes through the watch option.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.

Update template with model changed from input vueJS

I'm developing my first app in vueJs and laravel.
now I 'have a problem with v-model.
I have a page with component Person that edit or create new Person.
So I get from my backend in laravel or Model Person or new Person.
Now in my frontend I pass data to component by props:
Page.blade.php
<Person :person-data="{!! jsonToProp($person) !!}"></Person>
(jsonToProp transform model coming from backend in json)
In this case, I would return new Model so without properties, so $person will be a empty object.
Person.vue
<template>
<div>
<label for="name_p"> Name</label>
<input id="name_p" v-model="person.name" class="form-control" />
<button v-on:click="test()">test</button>
{{person.name}}
</div>
</template>
<script>
export default {
props: ['personData'],
mounted() {
},
data() {
return {
person: this.personData
}
},
methods:{
test(){
console.log(this.person.name);
}
}
}
</script>
Now if I change input with model v-model="person.name" I would print name in template but it doesn't change.
But if I click buttonit console write right value.
So I read that changing model value is asynch, so How I can render new Model when change input?
You should declare all the properties up front, as per the documentation:
Why isn’t the DOM updating?
Most of the time, when you change a Vue instance’s data, the view updates. But there are two edge cases:
When you are adding a new property that wasn’t present when the data was observed. Due to the limitation of ES5 and to ensure consistent behavior across browsers, Vue.js cannot detect property addition/deletions. The best practice is to always declare properties that need to be reactive upfront. In cases where you absolutely need to add or delete properties at runtime, use the global Vue.set or Vue.delete methods.
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is just syntax sugar for arr.splice(index, 1, value).
That may be because your data, which comes from jsonToProp($person) does not reactive.
You see, vue modify each object to make it 'reactive', some times you need to modify it by your own. detection caveats
Try to do this.person = Object.assign({}, this.person, this.personData) in your mounted hook, to make it reactive.