How to pass dynamic props with Vue - vue.js

I need to pass props using Vue, I thought of JSON with object that includes name and value. I need to pass data to a different component but it changes as in each event the names and values change.
So for example I might have name: 'a' value: 'b', name: 'f' value: 'k' and in anorher event name: 'c' value: 'd'
my code that works but it work because i return hard coded data
data() {
return {
params: {
name:'bill',
value:'jones'
},
in child
#Component({
props:
{
urls: {
type: Object,
default: () => { return {name:'', value: ''} }
},
}
function with object params that i need to get the data from
getParams(url) {
paramsData[key] = value;
//console.log(key,value);
}
return params;
console.log(params)

You can use computed property names
emitEvent(name, value) {
let objectToEmit = {
[name]: value,
};
this.$emit("event-name", objectToEmit);
}
Now name and value will be set according to whatever you pass in emitEvent function.
You can read more about computed property names on below link
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer

You can pretty much pass anything through and you can do it in many ways. Here are 4 examples:
Note: for all options we are assuming you have a parent component that is using the following syntax
<example-component :name="somename" :value="somevalue"></example-component>
Option1: props as a list of array of strings. The values can be anything in JS i.e. numbers, strings, objects, functions
<template>
<div>
<p v-text="example"></p>
</div>
</template>
<script>
export default {
name: "ExampleComponent",
prop: ['name','value']
}
</script>
Option 2: most common approach. Every prop to be a specific type of value. In these cases, you can list props as an object, where the properties’ names and values contain the prop names and types, respectively
<template>
<div>
<p v-text="example"></p>
</div>
</template>
<script>
export default {
name: "ExampleComponent",
props: {
name: {
type: String,
required: false,
default: 'something'
},
value: {
type: Number,
required: true
}
},
}
</script>
Option 3: you can validate or in this case pass in an Object and defaults returned from a factory function so you will always have some value. You can even return validation validator: function (value) {...}
<template>
<div>
<!-- some value from EventServiceClass-->
<p v-text="example.name"></p>
</div>
</template>
<script>
import EventServiceClass from "./EventServiceClass";
export default {
name: "ExampleComponent",
props: {
example: {
type: Object,
default: function () {
return {name:'a', value: 'b'}
}
},
},
}
</script>
Option 4: a little more advanced but in this example we are bind get and set to input properties on a form, which we would use to create factory form components
<template>
<div>
<input
id="name"
type="text"
v-model="name"
class="form--input light"/>
</div>
</template>
<script>
export default {
name: "ExampleComponent",
props: {
name: {
type: String,
default: ""
}
},
computed: {
name: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
}
}
}
}
</script>

Related

Can't pass value prop to custom input component

I have a custom input component that generally works well. But since yesterday I want to pass to it a value from the parent component in certain cases (namely if a cookie is found for that field, pre-fill the field with the cookie value).
Parent component (simplified):
<custom-input
v-model="userEmail"
value="John Doe"
/>
But for a reason I cannot comprehend, the value prop doesn't work. Why not?
My custom input component (simplified):
<template>
<input
v-bind="$attrs"
:value="value"
#blur="handleBlur"
>
</template>
<script>
export default {
inheritAttrs: false,
props: {
value: {
type: String,
default: ''
}
},
mounted () {
console.log(this.value) // displays nothing, whereas it should display "John Doe"
},
methods: {
handleBlur (e) {
this.$emit('input', e.target.value)
}
}
}
</script>
value prop is used with the emitted event input to do the v-model job, so you should give your prop another name like defaultValue to avid this conflict:
<custom-input
v-model="userEmail"
defaultValue="John Doe"
/>
and
<template>
<input
v-bind="$attrs"
:value="value"
#blur="emitValue($event.target.vaklue)"
>
</template>
<script>
export default {
inheritAttrs: false,
props: {
value: {
type: String,
default: ''
},
defaultvalue: {
type: String,
default: ''
},
},
mounted () {
this.emitValue(this.defaultValue)
},
methods: {
emitValue(val) {
this.$emit('input', val)
}
}
}
</script>

Update child component value on axios response using v-model

Vue 3
I am trying to update the value of the data variable from the Axios response. If I print the value in the parent component it's getting printed and updates on the response but the variable's value is not updating in the child component.
What I am able to figure out is my child component is not receiving the updated values. But I don't know why is this happening.
input-field is a global component.
Vue 3
Parent Component
<template>
<input-field title="First Name" :validation="true" v-model="firstName.value" :validationMessage="firstName.validationMessage"></input-field>
</template>
<script>
export default {
data() {
return {
id: 0,
firstName: {
value: '',
validationMessage: '',
},
}
},
created() {
this.id = this.$route.params.id;
this.$http.get('/users/' + this.id).then(response => {
this.firstName.value = response.data.data.firstName;
}).catch(error => {
console.log(error);
});
},
}
</script>
Child Component
<template>
<div class="form-group">
<label :for="identifier">{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input :id="identifier" :type="type" class="form-control" :class="validationMessageClass" :placeholder="title" v-model="inputValue">
<div class="invalid-feedback" v-if="validationMessage">{{ validationMessage }}</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
validation: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
default: 'text',
},
validationMessage: {
type: String,
required: false,
default: '',
},
modelValue: {
required: false,
default: '',
}
},
emits: [
'update:modelValue'
],
data() {
return {
inputValue: this.modelValue,
}
},
computed: {
identifier() {
return this.title.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
},
validationMessageClass() {
if (this.validationMessage) {
return 'is-invalid';
}
return false;
}
},
watch: {
inputValue() {
this.$emit('update:modelValue', this.inputValue);
},
},
}
</script>
The reason your child will never receive an update from your parent is because even if you change the firstName.value your child-component will not re-mount and realize that change.
It's bound to a property that it internally creates (inputValue) and keeps watching that and not the modelValue that's been passed from the parent.
Here's an example using your code and it does exactly what it's supposed to and how you would expect it to work.
It receives a value once (firstName.value), creates another property (inputValue) and emits that value when there's a change.
No matter how many times the parent changes the firstName.value property, the child doesn't care, it's not the property that the input v-model of the child looks at.
You can do this instead
<template>
<div class="form-group">
<label :for="identifier"
>{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input
:id="identifier"
:type="type"
class="form-control"
:class="validationMessageClass"
:placeholder="title"
v-model="localValue" // here we bind localValue as v-model to the input
/>
<div class="invalid-feedback" v-if="validationMessage">
{{ validationMessage }}
</div>
</div>
</template>
<script>
export default {
... // your code
computed: {
localValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
};
</script>
We remove the watchers and instead utilize a computed property which will return the modelValue in it's getter (so whenever the parent passes a new value we actually use that and not the localValue) and a setter that emits the update event to the parent.
Here's another codesandbox example illustrating the above solution.

Return model value formatted with Vue.js

I have a table in Vue.js application, listing a URL and an Id. This URL is defined by the user, so I created and input component, with a input text, using the URL as value and the Id as parameter. The v-model of this component is an array, where I need to store data as JSON objects like this:
{
"id": 1,
"url": "www.some-url.com"
}
How can I catch changes in the url field and return for its parent to append in an array?
Component:
<template>
<div class="row">
<div class="col-md-12">
<input type="text"
class="form-control"
v-model="value.url">
</div>
</div>
</template>
<script>
export default {
name: 'inputUrl',
props: {
url: {
type: [String],
description: 'URL'
},
id: {
type: Number,
description: 'Id'
}
},
components: {
}
data() {
return {
value: {
id: this.id,
url: this.default
}
};
},
methods: {
},
mounted() {
},
watch: {
}
}
</script>
Usage:
<inputUrl
:id="1"
url="www.some-url.com"
v-model="array">
</inputUrl>
I passed the array variable to the InputUrl component then used v-on directive to passing the current input value to a custom function for appending the new values to the array variable.
Here an example.

How to use v-model and computed properties on Input fields?

I have a simple component with 2 input fields:
<template>
<div>
<input type="text" v-model="name">
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data() {
return {
name: "",
alias: ""
}
}
}
</script>
I want to automatically insert the name model's value to the alias field IF the alias field is empty. If it's not empty, I want it to use its own value.
Here is my attempt:
<template>
<div>
<input type="text" v-model="name">
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data() {
return {
name: "",
alias: ""
}
},
computed: {
alias: {
get: function() {
if (this.alias.length < 1) {
return this.name
} else {
return this.alias
}
},
set: function(newValue) {
this.alias = newValue
}
}
}
}
</script>
The problem is that alias doesn't get the actual value in the alias property if I type something into the name field. I can only see it visually - but it doesn't hold the value. However, if I type into the alias field, it gets updated properly.
What am I missing here and how can I make it the way I want it?
Computed won't work because it should be treated as immutable.
Also because the model will be updated on each input, a watch won't work either, it would only pick up the first char of what you enter, unless its pre-populated.
This is how I would do it, simply add a #blur event on the name input then fire a method which populates alias if it's empty and on alias in case they empty it out.
The same method could be used in mounted, if you pre-populate the models, or you could watch it.
{
template: `
<div>
<input type="text" v-model="name" #blur="setAlias()">
<input type="text" v-model="alias" #blur="setAlias()">
</div>
`,
data() {
return {
name: '',
alias: ''
}
},
methods: {
setAlias() {
if (this.alias === '') {
this.alias = this.name
}
}
}
}
Firstly, you cannot have a computed property and a data property with the same name. Since both computed and data properties end up as properties on the same state object, one will overwrite the other.
Secondly, and I think you did this because of the first point, in your computed alias getter, your reference the alias again, which is essentially referencing itself, and looks like it could give some inconsistent return values.
I can think of two solutions to your issue:
1) Use a watcher on name:
Create a watcher function for name, and in it set this.alias to the same value as name when alias is blank, or if it's the same as the previous name value.
<script>
export default {
data: () => ({
name: "",
alias: ""
}),
watch: {
name(newVal, oldVal) {
if (!this.alias || this.alias === oldVal) {
this.alias = newVal;
}
}
}
}
</script>
2) Use explicit :value and #change/#keyup bindings on the name input:
v-model is a convenience method that sets both of these for you, but in your case you want to do some more logic in the change handler that just setting a state property value.
<template>
<div>
<input
type="text"
:value="name"
#keyup="onNameInput"
/>
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data: () => ({
name: "",
alias: ""
}),
methods: {
// Check and set both values on name input events
onNameInput(e) {
if (!this.alias || this.alias === this.name) {
this.alias = e.target.value;
}
this.name = e.target.value;
}
}
}
</script>

Two way data binding with :model.sync when prop has get() and set()

I have a computed property that I use as v-model on an input. I've written it this way to get reactivity -- this calls my setText Vuex action which I then can get with my getter text. It looks like this:
text: {
get() {
return this.text;
},
set(value) {
this.setText(value);
},
},
and I use it in my input like this:
<input class="input" type="text" v-model="text" />
This works well. Now, I've put the input in question into a separate component which I use. This means I have to pass the text v-model as props, which I do with :model.sync, like so:
<myInput :model.sync="text"/>
and in the myInput component I use the props like so:
<input class="input" id="search-order" type="text" :value="model" #input="$emit('update:model', $event)">
But this doesn't seem to work at all, whenever I type into the input, the input says: [object InputEvent] and if I try to see and the value of model it's {isTrusted: true}. I'm assuming it's because of the getters and setters I have on my computed property. How do I pass these down to the child component?
Instead of using the .sync modifier you can support the v-model directive in your custom component. v-model is syntax sugar for a value prop and an input event.
To support v-model just make sure your custom component has a value prop and emits an input event with the new value: this.$emit('input', event.target.value).
Here is an example of a <BaseInput> component I use, it's written in TypeScript:
<template>
<input
:type="type"
:value="value"
class="input"
v-on="listeners"
>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: 'BaseInput',
props: {
type: {
type: String,
default: 'text',
},
value: {
type: [String, Number],
default: '',
},
lazy: {
type: Boolean,
default: false,
},
number: {
type: Boolean,
default: false,
},
trim: {
type: Boolean,
default: false,
},
},
computed: {
modelEvent(): string {
return this.lazy ? 'change' : 'input'
},
parseModel(): (value: string) => string | number {
return (value: string) => {
if (this.type === 'number' || this.number) {
const res = Number.parseFloat(value)
// If the value cannot be parsed with parseFloat(),
// then the original value is returned.
return Number.isNaN(res) ? value : res
} else if (this.trim) {
return value.trim()
}
return value
}
},
listeners(): Record<string, Function | Function[]> {
return {
...this.$listeners,
[this.modelEvent]: (event: HTMLElementEvent<HTMLInputElement>) =>
this.$emit(this.modelEvent, this.parseModel(event.target.value)),
}
},
})
</script>
You can use it like so:
<BaseInput v-model="text" />