VueJs - bind input to url param - javascript

I would like to bind an input field to a route param in vue.js.
<input v-model="$route.params.query"/>
Is it possible?

The most straight-forward way I found was the following:
<input v-model="foo" />
--
data() {
return {
foo: this.$route.query.foo
};
},
watch: {
foo(newVal) {
this.$router.push({ query: { ...this.$route.query, foo: newVal } });
},
'$route.query.foo': function(val) {
this.foo = val;
}
}
Edit 2019-08-16: added watch for $route to react to back navigation.

A bit of time has passed, but since the docs say "it is often a better idea to use a computed property rather than an imperative watch callback" I thought I would add this pattern which I tested successfully and also looks more elegant IMHO.
The idea is to use a computed property linked to the query param with explicit get and set methods and bind the input with v-model to that property.
So in your template:
<input v-model="query_param"/>
Then in your computed properties:
computed: {
query_param: {
get() {
return this.$route.query.your_query_param
},
set(value) {
/* Here I use replace so that you're not actually
pushing a new page to the history stack */
this.$router.replace({
query: {
...this.$route.query,
your_query_param: value
}
})
}
}
}
This of course would work for a single query parameter. If you have multiple inputs you want to bind to different parameters just put additional computed properties in the same way.

Yes, it is possible. In my code, I have a <select> element on change of which I want to append the selected value to url as query. So, I have done like this:
My previous URL was:
localhost:8080/books
<select class="form-control" v-on:change="appendQueryToUrl" v-model="filterData.genre">
methods: {
appendQueryToUrl() {
this.$router.push({query: {genre: this.filterData.genre}})
}
}
Where genre is my key and this.filterData.genre is my local object value. Now my url looks like:
localhost:8080/books/?genre=Computer
Thanks and inform me if any error.

Related

Why won't my template update with it being bound to a computed property?

I am facing an issue where I have some template HTML in a component that relies on the computed getter of a Vuex method. As you can see in the template, I am simply trying to show the output of the computed property in a <p> tag with {{ getNumSets }}.
As I update the state with the UPDATE_EXERCISE_SETS mutation, I can see in the Vue devtools that the state is updated correctly, but the change is not reflected in the <p> {{ getNumSets }} </p> portion.
Template HTML:
<template>
...
<v-text-field
v-model="getNumSets"
placeholder="S"
type="number"
outlined
dense
></v-text-field>
<p>{{ getNumSets }}</p>
...
</template>
Component Logic:
<script>
...
computed: {
getNumSets: {
get() {
var numSets = this.$store.getters['designer/getNumSetsForExercise']({id: this.id, parent: this.parent})
return numSets
},
set(value) { // This correctly updates the state as seen in the Vue DevTools
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
})
}
}
...
</script>
Vuex Store Logic:
...
state: {
designerBucket: []
},
getters: {
getNumSetsForExercise: (state) => (payload) => {
var numSets = 0
for (var i = 0; i < state.designerBucket.length; i++) {
if (state.designerBucket[i].id == payload.id) {
numSets = state.designerBucket[i].numSets
}
}
return numSets
}
},
mutations: {
UPDATE_EXERCISE_SETS(state, payload) {
state.designerBucket.forEach(exercise => {
if (exercise.id == payload.id) {
exercise.numSets = payload.numSets
}
})
}
}
Any insight is very appreciated!
P.S. I have also tried using a for (var i=0...) loop, looping over the indices and then using Vue.set() to set the value. This did update the value in the store as well, but the computed property is still not updating the template.
This turned into a bit of a long-winded answer, but bear with me.
Here's my hunch: since you're returning a function from your Vuex getter, Vue isn't updating your computed property on state changes because the function never changes, even if the value returned from it would. This is foiling the caching mechanism for computed properties.
Reactivity for Arrow Function Getters
One of the things to keep in mind when creating a getter like this, where you return an arrow function:
getNumSetsForExercise: (state) => (payload) => {
var numSets = 0
for (var i = 0; i < state.designerBucket.length; i++) {
if (state.designerBucket[i].id == payload.id) {
numSets = state.designerBucket[i].numSets
}
}
return numSets
}
...is that you're no longer returning actual state data from your getter.
This is great when you're using it to pull something from state that depends on data that's local to your component, because we don't need Vue to detect a change, we just need the function to access current state, which it does fine.
BUT, it may also lead to the trap of thinking that updating state should update the getter, when it actually doesn't. This is really only important when we try to use this getter in a computed property like you have in the example, due to how computed properties track their dependencies and cache data.
Computed Caching and Dependency Detection
In Vue, computed properties are smarter than they first seem. They cache their results, and they register and track the reactive values they depend on to know when to invalidate that cache.
As soon as Vue calculates the value of a computed property, it stores it internally, so that if you call the property again without changing dependencies, the property can return the cached value instead of recalculating.
The key here for your case is the dependency detection– your getter has three dependencies that Vue detects:
get() {
var numSets = this.$store.getters['designer/getNumSetsForExercise']({id: this.id, parent: this.parent})
return numSets
},
The getter: this.$store.getters['designer/getNumSetsForExercise']
this.id
this.parent
None of these values change when <v-text-field> calls your setter.
This means that Vue isn't detecting any dependency changes, and it's returning the cached data instead of recalculating.
How to Fix it?
Usually, when you run into these sorts of dependency issues, it's because the design of the state could be improved, whether by moving more data into state, or by restructuring it in some way.
In this case, unless you absolutely need designerBucket to be an array for ordering purposes, I'd suggest making it an object instead, where each set is stored by id. This would simplify the implementation by removing loops, and remove the need for your getter altogether:
...
state: {
designerBucket: {}
},
mutations: {
UPDATE_EXERCISE_SETS(state, payload) {
// Need to use $set since we're adding a new property to the object
Vue.set(state.designerBucket, payload.id, payload.numSets);
}
}
Now, instead of invoking a getter, just pull designerBucket from state and access by this.id directly:
<script>
...
computed: {
getNumSets: {
get() {
return this.$store.state.designerBucket[this.id];
},
set(value) {
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
});
}
}
...
</script>
This should allow Vue to detect changes correctly now, and prevent the stale cache problem from before.
Edited: First import mapGetters from 'vuex' like this on the top of the script tag.
import { mapGetters } from "vuex"
Now in your computed object, add mapGetters and pass arguments to the getter method inside the get() method like this:-
computed: {
...mapGetters('designer',['getNumSetsForExercise']),
getNumSets: {
get() {
var numSets = this.getNumSetsForExercise({id: this.id, parent: this.parent})
return numSets
},
set(value) { // This correctly updates the state as seen in the Vue DevTools
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
})
}
}
And see if it works.

How to use Vue.js methods to set data properties

I'm new to Vue.js and trying to display data that is requested from a server. I have created a new Vue app and defined a method:
methods: {
getData: function() {
// making a request, parsing the response and pushing it to the array.
console.log(arr);
return arr;
}
}
The method works OK and I can log the array to the console on a button click.
<button v-on:click="getData">Get me some data</button>
However, I'm not sure how to actually use this array in the app. I would like save it to a property and then display it for the user. At first, I was thinking that I could use computed properties like this:
computed: {
values: function() {
return this.getData;
}
}
... and display it to the user with a for loop:
<p v-for="value in values">{{ value }}></p>
At least this solution did not produce the desired result. I've probably misunderstood some of Vue's logic here.
You need to use data property.
data() {
return {
values: []
}
},
methods: {
getData: function() {
this.values = arr;
}
}
And loop the values

Vuex commit after await won't update state

I've read multiple similar questions about this here and elsewhere, but I can't figure it out.
I have a form with mapGetters and input values that should update based on Vuex state:
...mapGetters({
show: "getShow"
}),
sample form input (I'm using Bootstrap Vue):
<b-form-input
id="runtime"
name="runtime"
type="text"
size="sm"
v-model="show.runtime"
placeholder="Runtime"
></b-form-input>
Then I have this method on the form component:
async searchOnDB() {
var showId = this.show.showId;
if (!showId) {
alert("Please enter a showId");
return;
}
try {
await this.$store.dispatch("searchShowOnDB", showId);
} catch (ex) {
console.log(ex);
alert("error searching on DB");
}
},
and this action on the store:
async searchShowOnDB({ commit, rootState }, showId) {
var response = await SearchAPI.searchShowOnDB(showId);
var show = {
show_start: response.data.data.first_aired,
runtime: response.data.data.runtime,
description: response.data.data.overview
};
//I'm updating the object since it could already contain something
var new_show = Object.assign(rootState.shows.show, show);
commit("setShow", new_show);
}
mutation:
setShow(state, show) {
Vue.set(state, "show", show);
}
searchAPI:
export default {
searchShowOnDB: function (showId) {
return axios.get('/search/?id=' + showId);
},
}
Everything works, the API call is executed, I can even see the Vuex updated state in Vue Devtools, but the form is not updated.
As soon as I write something in an input field or hit commit in Vue Devtools, the form fields show_start, runtime, description all get updated.
Also, this works correctly and updates everything:
async searchShowOnDB({ commit, rootState }, showId) {
var show = {
show_start: "2010-03-12",
runtime: 60,
description: "something"
};
//I'm updating the object since it could already contain something
var new_show = Object.assign(rootState.shows.show, show);
commit("setShow", new_show);
}
I don't know what else to do, I tried by resolving Promises explicitly, remove async/await and use axios.get(...).then(...), moving stuff around... nothing seems to work.
On line 15 of your /modules/search.js you're using Object.assign() on rootState.search.show. This mutates the search prop of the state (which is wrong, btw, you should only mutate inside mutations!). Read below why.
And then you're attempting to trigger the mutation. But, guess what? Vue sees it's the same value, so no component is notified, because there was no change. This is why you should never mutate outside of mutations!
So, instead of assigning the value to the state in your action, just commit the new show (replace lines 15-16 with:
commit('setShow', show);
See it here: https://codesandbox.io/s/sharp-hooks-kplp7?file=/src/modules/search.js
This will completely replace state.show with show. If you only want to merge the response into current state.show (to keep some custom stuff you added to current show), you could spread the contents of state.show and overwrite with contents of show:
commit("setShow", { ...rootState.search.show, ...show });
Also note you don't need Vue.set() in your mutation. You have the state in the first parameter of any mutation and that's the state of the current module. So just assign state.show = show.
And one last note: when your vuex gets bigger, you might want to namespace your modules, to avoid any name clashes.
All props of objects in a state that is used in templates must exist or you should call Vue.set for such properties.
state: {
show: {
runtime: null // <- add this line
}
},
You call Vue.set for the whole object but it already exist in the state and you do not replace it by a new one you just replace props. In your case you have an empty object and add the 'runtime' prop it it using Object.assign.
Also all manipulations with state should be done in mutations:
var new_show = {
runtime: response.data.url
};
commit("setShow", new_show);
...
mutations: {
setShow(state, new_show) {
Object.assign(state.show, new_show)
}
},

Vue.js 2: Cant useTwo-way Computed Property in combination with vuex

I cant get Two-way Computed Property in combination with vuex to work.
If there are input changes I want to set getIsUnsavedData to true and "copy"/commit the changes into a new variable $store.state.authenticatedUser.changedData. After there is any change I want the input to get() its value from $store.state.authenticatedUser.changedData instead of $store.state.authenticatedUser.data to display the change.
At fist everything works like expected. If there are input changes, the changed value will be replicated in the $store.state.authenticatedUser.changedData property. The getIsUnsavedData value changes to true and the get() points to the replicated data.
There is only one bug left. Suddenly the computed property never changes although the vuex store is updating correctly. The set() function still gets called, but the get() doesn't .
<ui-textbox #change="valueChanged" v-model="userName"></ui-textbox>
// ...
computed: {
userName: {
get() {
return this.$store.getters.getIsUnsavedData ? this.$store.state.authenticatedUser.changedData.name : this.$store.state.authenticatedUser.data.name
},
set(value) {
this.$store.commit('setChangedUserData', {
key: 'name',
value: value
})
}
}
},
methods: {
valueChanged() {
this.$store.commit('setUnsavedState', true)
}
},
// ....
Try to use mine library
https://github.com/yarsky-tgz/vuex-dot
it's done for such situations, to make code footprint of setters/getters in your code much less.
<template>
<form>
<input v-model="name"/>
<input v-model="email"/>
</form>
</template>
<script>
import { takeState } from 'vuex-dot';
export default {
computed: {
...takeState('user')
.expose(['name', 'email'])
.dispatch('editUser')
.map()
}
}
</script>
(updated to reflect Andor's input)
v-model can point to a computed property but it needs to handle work a bit differently than when it just refers to data.
vue's doc
applying the section on two-way computed properties:
<ui-textbox v-model="userName"></ui-textbox>
// ...
computed: {
userName: {
get () {
return this.$store.state.obj.userName
},
set (value) {
this.$store.commit('updateUserName', value)
}
}
}
I might be missing something about what you're doing but this is how I'd start to solve the problem.

Polymer readOnly access private setter for given property

I am aware you can set a readOnly property by using the generated setter: _setMyProp(newValue)
But what if I don't know the name of the property before-hand? Is there any way to access the setter for a given property? My current solution is mapping property names to their setter functions:
_setters: {
type: Object,
value: {
prop1 : function(newVal) { this._setProp1(newVal); },
prop2 : function(newVal) { this._setProp2(newVal); }
}
}
Is there a more elegant way to do this?
Update
For more context, this is the function that calls _setters. This function should ideally not use any actual property names. This is what I meant by "don't know the name of the property before-hand"
load: function(data, key) {
if (!_.isEmpty(data)) {
if (key) {
this._setters[key].bind(this)(data);
} else {
_.each(this._setters, function(setter, key) {
setter.bind(this)(data[key]);
}.bind(this));
}
}
},
The element behaves like iron-meta and loads data from a global object when it's ready, and sets all it's properties.
ready: function() {
this.load(_data);
},
But it must also load specific properties when an ajax response comes in, for each attached element.
_els.forEach(function(el) {
el.load(key, _data[key]);
});
I'm trying to make it as easy as possible to define more properties later on. Polymer's this.set('propName', 'value'); would be perfect but does not work for readOnly properties.
If I should just scrap this entirely and go a different route, I'd also be open to that advice.
Update 2
My solution for lack of a better one was to abandon readOnly and just use this.set
This is what load looks like now:
load: function(data, property) {
if (!_.isEmpty(data)) {
if (property) {
this.set(property, data[property]);
} else {
Object.keys(this.properties).forEach(function(property) {
this.set(property, data[property]);
}.bind(this));
}
}
},

Categories

Resources