How to make property in Vue component to be reactive - vue.js

I'm trying to create a new component:
Vue.component('my-component', {
props: {
displayed: {
type: Boolean
}
},
template: `
<div v-bind:class="{'modal': true, 'auth-required': true, 'show-modal': displayed }">
<div class="modal__content">
<img src="/img/popup/close.svg" v-on:click="displayed = false;" alt="close" class="modal__closeBtn modal__closeBtn-questions" />
<slot></slot>
<img src="/img/popup/dog.png" alt="dog" class="modal__contentImg" />
</div>
</div>`,
data: function () {
return {
isDisplayed: this.displayed
};
},
})
But when i'm trying to bind displayed property to another property from the page it doesn't work when modal.authRequired value changes:
<mycomponent :displayed="modal.authRequired"></mycomponent>
How to make isDisplayed to be reactive when modal.authRequired changes

Add a watch to your prop:
Vue.component('my-component', {
props: {
displayed: {
type: Boolean
}
},
template: `
<div v-bind:class="{'modal': true, 'auth-required': true, 'show-modal': isDisplayed }">
<div class="modal__content">
<img src="/img/popup/close.svg" v-on:click="displayed = false;" alt="close" class="modal__closeBtn modal__closeBtn-questions" />
<slot></slot>
<img src="/img/popup/dog.png" alt="dog" class="modal__contentImg" />
</div>
</div>`,
data: function () {
return {
isDisplayed: this.displayed
};
},
watch: {
displayed(newValue) {
// Update the data value
this.isDisplayed = newValue;
}
}
})
Also, notice that I changed the 'show-modal' binding: 'show-modal': isDisplayed

Reactive properties are the computed one :https://v2.vuejs.org/v2/guide/computed.html

Related

how to send click button's value to a props

In my parent component I have a button and child component:
<div> #click="editMe(edit)" />
<edit-team :openModal="openModal"/>
</div>
export default {
data() {
return {
openModal: false,
};
},
method: {
editMe(edit) {
this.openModal = true;
},
}
}
So after I click the editMe button, I am expecting openModal becomes true and goes true to child component.
<template>
<el-dialog
:visible.sync="modal"
/>
</template>
<script>
export default {
props: {
openModal: {
type: Boolean,
required: true,
},
},
data() {
return {
modal: this.openModal,
};
},
</script>
But unfortunately, the modal is not opening because props comes as false always. I assigned openModal to new variable because it was giving me a warning about mutating props. So how do you think I can send the props in right value?
In child just try this
<template>
<el-dialog
:visible.sync="openModal"
/>
</template>
<script>
export default {
props: {
openModal: {
type: Boolean,
required: true,
},
},
},
</script>
If you have sibilngs components and you need to retrieve data, you can use the emit keyword and emit events
Then it will work like this :
The sibling emit the event to show the modal
The parent update the showModalData to true
This child is re-rendered
Vue.component('modal', {
template: '#modal',
props: ['show'],
});
Vue.component('buttonModal', {
template: '#buttonModal',
methods: {
showModal(){
this.$emit('show-modal-button', true)
}
}
});
new Vue({
el: '#app',
data: () => ({
showModalData: false
}),
methods: {
editMe() {
this.showModalData = true
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<p>this is the parent</p>
<hr>
<button-modal #show-modal-button="editMe">Show modal</button-modal>
<hr>
<modal :show="showModalData" />
</div>
<template id="modal">
<div>
props show : {{ show }}
<h2 v-if="show">This is the modal</h2>
</div>
</template>
<template id="buttonModal">
<div>
<p>This is the sibilng</p>
<button #click="showModal"> Show the modal through sibiling components </button>
</div>
</template>

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.

Vue resets child Component data on props update

I have child component, that has some internal data, that should not be changed from outside. But when I update prop from parent component, this internal data is reset.
Basically in example below, when we change title from outside, value is set back to empty ''. How can I make value persistent with Child component props update?
Child.vue
<template>
<div class="child">
<h2>{{title}}</h2>
<input type="text" :value="value" v-on:change="$emit('change', $event.target.value)">
</div>
</template>
<script>
export default {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
}
}
</script>
Parent.vue
<template>
<div class="parent">
<Child :title="title" v-on:change="processTitle($event)"></Child>
</div>
</template>
<script>
import Child from './Child';
export default {
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.reverse();
}
}
}
</script>
You are not setting the value data attribute, :value=value means that "if value changes, the input value should pick up that change". But value doesn't change. Use v-model instead if you want to keep it simple.
Vue.component("Child", {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
},
template: `
<div class="child">
<h2>{{title}}</h2>
<input type="text" v-model="value" v-on:change="$emit('change', $event.target.value)">
</div>
`
})
new Vue({
el: "#app",
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.split("").reverse().join("");
}
},
template: `
<div class="parent">
<child :title="title" v-on:change="processTitle($event)"></child>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
EDIT
Also, if you want a continuous effect, don't use #change - use #input instead:
Vue.component("Child", {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
},
template: `
<div class="child">
<h2>{{title}}</h2>
<input type="text" v-model="value" v-on:input="$emit('change', $event.target.value)">
</div>
`
})
new Vue({
el: "#app",
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.split("").reverse().join("");
}
},
template: `
<div class="parent">
<child :title="title" v-on:change="processTitle($event)"></child>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

how to enable v-model binding when building a custom components from other custom components

I am able to build a simple textbox component from <input /> and setup v-model binding correctly.
I'm trying to do same with a custom component: vs-input from vuesax.
Following the pattern below does not work as expected:
<template>
<div>
<vs-input type="text" v-model="value" #input="text_changed($event)" />
<!-- <input type="text" :value="value" #input="$emit('input', $event.target.value)" /> -->
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
methods: {
text_changed(val) {
console.log(val)
// this.$emit('input', val)
}
}
}
</script>
In building custom components from other custom components is there anything particular we should look out for to get v-model binding working properly?
Following code might help you.(Sample code try it in codepen)
updating props inside a child component
//html
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<input type="text" :value="test" #change="abc">
{{ test }}
</div>
//VUE CODE
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
},
props:{
test:{
type:String,
default:''
}
},
methods:{
abc:function(event){
//console.log("abc");
console.log(event.target.value);
this.test=event.target.value;
}
}
})
I prefer to interface props with computed:
<template>
<div>
<vs-input type="text" v-model="cValue" />
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
computed: {
cValue: {
get: function(){
return this.value;
},
set: function(val){
// do w/e
this.$emit('input', val)
}
}
}
}
</script>
Computed Setter

Vuejs v-on click doesn't work inside component

I use VueJs and I create the following component with it.
var ComponentTest = {
props: ['list', 'symbole'],
data: function(){
return {
regexSymbole: new RegExp(this.symbole),
}
},
template: `
<div>
<ul>
<li v-for="item in list"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
`,
methods: {
replaceSymbole: function(name){
return name.replace(this.regexSymbole, '<span v-on:click="test">---</span>');
},
test: function(event){
console.log('Test ...');
console.log(this.$el);
},
}
};
var app = new Vue({
el: '#app',
components: {
'component-test': ComponentTest,
},
data: {
list: [{"id":1,"name":"# name1"},{"id":2,"name":"# name2"},{"id":3,"name":"# name3"}],
symbole: '#'
},
});
and this my html code
<div id="app">
<component-test :list="list" :symbole="symbole"></component-test>
</div>
When I click on the "span" tag inside "li" tag, nothing append.
I don't have any warnings and any errors.
How I can call my component method "test" when I click in the "span" tag.
How implement click event for this case.
You cannot use vue directives in strings that you feed to v-html. They are not interpreted, and instead end up as actual attributes. You have several options:
Prepare your data better, so you can use normal templates. You would, for example, prepare your data as an object: { linkText: '---', position: 'before', name: 'name1' }, then render it based on position. I think this is by far the nicest solution.
<template>
<div>
<ul>
<li v-for="(item, index) in preparedList" :key="index">
<template v-if="item.position === 'before'">
<span v-on:click="test">{{ item.linkText }}</span>
{{ item.name }}
</template>
<template v-else-if="item.position === 'after'">
{{ item.name }}
<span v-on:click="test">{{ item.linkText }}</span>
</template>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(question) {
if (question.indexOf("#") === 0) {
return {
linkText: "---",
position: "before",
name: question.replace("#", "").trim()
};
} else {
return {
linkText: "---",
position: "after",
name: question.replace("#", "").trim()
};
}
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
}
}
};
</script>
You can put the click handler on the surrounding li, and filter the event. The first argument to your click handler is the MouseEvent that was fired.
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id" v-on:click="clickHandler"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
data() {
return {
regexSymbole: new RegExp(this.symbole)
};
},
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(name) {
return name.replace(
this.regexSymbole,
'<span class="clickable-area">---</span>'
);
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
},
clickHandler(event) {
const classes = event.srcElement.className.split(" ");
// Not something you do not want to trigger the event on
if (classes.indexOf("clickable-area") === -1) {
return;
}
// Here we can call test
this.test(event);
}
}
};
</script>
Your last option is to manually add event handlers to your spans. I do not!!! recommend this. You must also remove these event handlers when you destroy the component or when the list changes, or you will create a memory leak.