How to monitor the data changes of the parent component in the child component - vue.js

When the data of the parent component changes, how to realize real-time monitoring and respond in the child component.

You can watch props when props changes:
<div id="app">
<childcomponent :myprop="text"></childcomponent>
<button #click="text = 'New text'">Change text</button>
</div>
Watch for changes in ChildComponent using watch method!
new Vue({
el: '#app',
data: {
text: 'This is Text'
},
components: {
'childcomponent' : {
template: `<p>{{ myprop }}</p>`,
props: ['myprop'],
watch: {
myprop: function(newValue, oldValue) {
console.log('Prop changed and new value is: ', newVal, ' | old value: ', oldValue);
}
}
}
}
});
https://v2.vuejs.org/v2/api/#watch

props: ["opType", "editData"],
watch: {
editData: {
immediate: true,
handler(newVal, oldVal) {
if (this.editData.pryType === "MOBN") {
this.infoIndex = 0;
} else if (this.editData.pryType === "EMAL") {
this.infoIndex = 1;
} else if (this.editData.pryType === "SVID") {
this.infoIndex = 2;
} else if (this.editData.pryType === "BBIN") {
this.infoIndex = 3;
if (this.getListSeconed) {
this.getkList();
this.getListSeconed = false;
}
}
},
deep: true
}
},
Can try like this.

Related

How to forceUpdate sibling component in VueJS

I have created a component which have two child components AddContactList and ViewContactLists, Here I need to forceUpdate ViewContactList when new entry inserted from AddContactList component
This is AddContactList components script
<script>
export default {
data() {
return {
fields: {},
errors: {},
success: false,
loaded: true,
}
},
methods: {
submit() {
if (this.loaded) {
this.loaded = false;
this.success = false;
this.errors = {};
console.log('Loading..');
axios.post('/submit', this.fields).then(response => {
this.fields = {}; //Clear input fields.
this.loaded = true;
this.success = true;
console.log('done..');
// --Here I need to update ViewContactList component
}).catch(error => {
this.loaded = true;
if (error.response.status === 422) {
console.log(error.response.data.errors)
this.errors = error.response.data.errors || {};
}
});
}
},
},
}
</script>
This is ViewContactList components script
<script>
import pagination from 'laravel-vue-pagination'
export default {
name:"ContactList",
components:{
pagination
},
data(){
return {
ContactList:{
type:Object,
default:null
}
}
},
mounted(){
this.list()
},
methods:{
async list(page=1){
await axios.get(`/getContactLists?page=${page}`).then(({data})=>{
this.ContactList = data
}).catch(({ response })=>{
console.error(response)
})
}
}
}
</script>
You can simply achieve this by emitting an event on successful save to the parent and then from parent component you can invoke the contact list component method with the help of ref.
Live demo :
Vue.component('childone', {
props: ['childmsg', 'childOneRef'],
template: `<p>{{ childmsg }} <button #click="$emit('savesuccess')">Add Contact</button></p>`
});
Vue.component('childtwo', {
props: ['childmsg', 'childoneref'],
template: '<p>{{ childmsg }}</p>',
methods: {
getupdtedList() {
console.log('Contact List call');
}
}
});
var app = new Vue({
el: '#app',
methods: {
callViewContactListCompMethod() {
this.$refs.contactListRef.getupdtedList();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<childone #savesuccess="callViewContactListCompMethod" childmsg="This is a child 1 message">
</childone>
<childtwo ref="contactListRef" childmsg="This is a child 2 message">
</childtwo>
</div>
If your parent component is ContactComponent
ContactComponent
|--AddContactList
|--ViewContactList
When you insert contact in add contact list emit the #addList event to the parent component.
Then pass the contact list as a props to ViewContactList.
when the props is changed, the ViewContactList component will be re-rendered automatically.

Why does Vuetify Autocomplete not selecting the data being set?

I use Vuetify autocomplete as a reusable component to display a list of jobs in key/value pairs. In creating record, the component works fine but when editing where data should be filled in, the data has the value but not showing on the component.
JobDropdownSelector.vue:
................................................................................................................................................
<template>
<v-autocomplete
v-model="job"
label="Job Designation"
item-value="id"
item-text="name"
return-object
:items="jobs"
#change="onChange"
>
</v-autocomplete>
</template>
<script>
export default {
props: {
selectedJob: {
type: Object,
default: null
}
},
data() {
return {
jobs: [],
job: null
};
},
methods: {
getDataFromApi() {
axios.get("api/jobs")
.then(response => {
this.jobs = response.data.data;
})
.catch(error => {
console.log(error);
reject();
});
},
onChange(e) {
this.job = e;
this.$emit("onChange", e);
}
},
watch: {
selectedJob: {
deep: true,
immediate: true,
handler(newValue, oldValue) {
this.job = newValue;
}
}
},
mounted() {
this.getDataFromApi();
}
};
</script>
EditForm.vue:
................................................................................................................................................
<template>
<div>
<JobDropdownSelector
v-model="job"
:selectedJob="job"
#onChange="onChangeJob"
>
</JobDropdownSelector>
</div>
</template>
<script>
import JobDropdownSelector from "../components/JobDropdownSelector";
export default {
components: {
JobDropdownSelector
},
data() {
return {
job: null
};
},
methods: {
onChangeJob(e) {
this.job = e;
},
getInitialJob() {
axios.get("api/jobs/22").then(response => {
this.job = response.data.data;
});
}
},
mounted() {
this.getInitialJob();
}
};
</script>
Display
Console
It looks like you overriding job twice: on the onChangeJob method and via v-model on JobDropdownSelector.
You can try use input event to update modek instead of emitting additional event.
JobDropdownSelector.vue
methods: {
onChange(e) {
// You don't need set job because it's connected via v-model.
// this.job = e;
this.$emit("input", e);
}
}
This will cause that job in EditForm.vue should update automatically after dropdown change.

listen to events from dynamic vue components

How would you listen to an event emitted by a dynamically created component instance?
In the example, the top component is added in the DOM, while the second is dynamically created in javascript.
Vue.component("button-counter", {
data: function() {
return {
count: this.initial_count
}
},
props: ['initial_count'],
methods: {
add: function() {
this.count++
this.$emit('myevent', this.count)
}
},
template: '<button v-on:click="add">You clicked me {{ count }} times.</button>'
})
let app = new Vue({
el: "#app",
data() {
return {
initial_count: 10,
}
},
mounted: function() {
let initial_count = this.initial_count
let ButtonCounterComponentClass = Vue.extend({
data: function() {
return {}
},
render(h) {
return h("button-counter", {
props: {
initial_count: initial_count
}
})
}
})
let button_counter_instance = new ButtonCounterComponentClass()
button_counter_instance.$mount()
button_counter_instance.$on('myevent', function(count) {
console.log('listened!')
this.say(count)
})
this.$refs.container.appendChild(button_counter_instance.$el)
},
methods: {
say: function(message) {
alert(message)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button-counter initial_count=20 v-on:myevent="say"></button-counter>
<div ref='container'></div>
</div>
If I've understood what you want then you just need to listen for the event on the inner component and pass it on.
I've used arrow functions in a couple of places to avoid problems with this bindings. Otherwise I've tried to leave your code unchanged as much as possible. Changes marked with ****.
Vue.component("button-counter", {
data: function() {
return {
count: this.initial_count
}
},
props: ['initial_count'],
methods: {
add: function() {
this.count++
this.$emit('myevent', this.count)
}
},
template: '<button v-on:click="add">You clicked me {{ count }} times.</button>'
})
let app = new Vue({
el: "#app",
data() {
return {
initial_count: 10,
}
},
mounted: function() {
let initial_count = this.initial_count
let ButtonCounterComponentClass = Vue.extend({
data: function() {
return {}
},
render(h) {
return h("button-counter", {
props: {
initial_count: initial_count
},
// **** Added this ****
on: {
myevent: count => {
this.$emit('myevent', count);
}
}
// ****
})
}
})
let button_counter_instance = new ButtonCounterComponentClass()
button_counter_instance.$mount()
// **** Changed the next line ****
button_counter_instance.$on('myevent', count => {
console.log('listened!')
this.say(count)
})
this.$refs.container.appendChild(button_counter_instance.$el)
},
methods: {
say: function(message) {
alert(message)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button-counter initial_count=20 v-on:myevent="say"></button-counter>
<div ref='container'></div>
</div>
It's important to understand that button_counter_instance is not an instance of your button-counter component. You've wrapped it in another component, albeit a component that doesn't add any extra DOM nodes. So listening on the wrapper component is not the same as listening on button-counter.
Docs for what you can pass to h: https://v2.vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth

Updating a prop inside a child component so it updates on the parent container too

So I have a simple template like so:
<resume-index>
<div v-for="resume in resumes">
<resume-update inline-template :resume.sync="resume" v-cloak>
//...my forms etc
<resume-update>
</div>
<resume-index>
Now, inside the resume-updatecomponent I am trying to update the prop on the inside so on the outside it doesn't get overwritten, my code is like so;
import Multiselect from "vue-multiselect";
import __ from 'lodash';
export default {
name: 'resume-update',
props: ['resume'],
components: {
Multiselect
},
data: () => ({
form: {
name: '',
level: '',
salary: '',
experience: '',
education: [],
employment: []
},
submitted: {
form: false,
destroy: false,
restore: false
},
errors: []
}),
methods: {
update(e) {
this.submitted.form = true;
axios.put(e.target.action, this.form).then(response => {
this.resume = response.data.data
this.submitted.form = false;
}).catch(error => {
if (error.response) {
this.errors = error.response.data.errors;
}
this.submitted.form = false;
});
},
destroy() {
this.submitted.destroy = true;
axios.delete(this.resume.routes.destroy).then(response => {
this.resume = response.data.data;
this.submitted.destroy = false;
}).catch(error => {
this.submitted.destroy = false;
})
},
restore() {
this.submitted.restore = true;
axios.post(this.resume.routes.restore).then(response => {
this.resume = response.data.data;
this.submitted.restore = false;
}).catch(error => {
this.submitted.restore = false;
})
},
reset() {
for (const prop of Object.getOwnPropertyNames(this.form)) {
delete this.form[prop];
}
}
},
watch: {
resume: function() {
this.form = this.resume;
},
},
created() {
this.form = __.cloneDeep(this.resume);
}
}
When I submit the form and update the this.resume I get the following:
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value. Prop being
mutated: "resume"
I have tried adding computed to my file, but that didn't seem to work:
computed: {
resume: function() {
return this.resume
}
}
So, how can I go about updating the prop?
One solution:
simulate v-model
As Vue Guide said:
v-model is essentially syntax sugar for updating data on user input
events, plus special care for some edge cases.
The syntax sugar will be like:
the directive=v-model will bind value, then listen input event to make change like v-bind:value="val" v-on:input="val = $event.target.value"
So the steps:
create one prop = value which you'd like to sync to parent component
inside the child component, create one data porperty=internalValue, then uses Watcher to sync latest prop=value to data property=intervalValue
if intervalValue change, emit one input event to notice parent component
Below is one simple demo:
Vue.config.productionTip = false
Vue.component('container', {
template: `<div>
<p><button #click="changeData()">{{value}}</button></p>
</div>`,
data() {
return {
internalValue: ''
}
},
props: ['value'],
mounted: function () {
this.internalValue = this.value
},
watch: {
value: function (newVal) {
this.internalValue = newVal
}
},
methods: {
changeData: function () {
this.internalValue += '#'
this.$emit('input', this.internalValue)
}
}
})
new Vue({
el: '#app',
data () {
return {
items: ['a', 'b', 'c']
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<p>{{items}}
<container v-for="(item, index) in items" :key="index" v-model="items[index]">
</container>
</div>
</div>
or use other prop name instead of value (below demo use prop name=item):
Also you can use other event name instead of event name=input.
other steps are similar, but you have to $on the event then implement you own handler like below demo.
Vue.config.productionTip = false
Vue.component('container', {
template: `<div>
<p><button #click="changeData()">{{item}}</button></p>
</div>`,
data() {
return {
internalValue: ''
}
},
props: ['item'],
mounted: function () {
this.internalValue = this.item
},
watch: {
item: function (newVal) {
this.internalValue = newVal
}
},
methods: {
changeData: function () {
this.internalValue += '#'
this.$emit('input', this.internalValue)
this.$emit('test-input', this.internalValue)
}
}
})
new Vue({
el: '#app',
data () {
return {
items: ['a', 'b', 'c']
}
},
methods: {
syncChanged: function (target, index, newData) {
this.$set(target, index, newData)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
Event Name=input
<p>{{items}}</p>
<container v-for="(item, index) in items" :key="index" :item="item" #input="syncChanged(items, index,$event)">
</container>
</div>
<hr> Event Name=test-input
<container v-for="(item, index) in items" :key="index" :item="item" #test-input="syncChanged(items, index,$event)">
</container>
</div>
I usually use vuex to manage variables that I will be using in multiple components and like the error says, load them in the various components using the computed properties. Then use the mutations property of the store object to handle changes
In component files
computed: {
newProfile: {
get() {
return this.$store.state.newProfile;
},
set(value) {
this.$store.commit('updateNewProfile', value);
}
},
In the vuex store
state: {
newProfile: {
Name: '',
Website: '',
LoginId: -1,
AccountId: ''
}
},
mutations: {
updateNewProfile(state, profile) {
state.newProfile = profile;
}
}

Changing value in watching cann`t work in some case with Vue

I want to change number to int in watch, but it doesn`t work.
index.vue:
<template>
<test v-model="value" />
</template>
<script>
import Test from './test.vue';
export default {
components: {
test: Test,
},
data() {
return {
value: 1,
};
},
watch: {
value(val) {
this.value = parseInt(val);
// this.value = 2;
}
}
};
</script>
test.vue:
<template>
<div class="el-input-number">
<input
:value="currentValue"
#input="handleInput"
ref="input"
/>
</div>
</template>
<script>
export default {
name: 'ElInputNumber',
props: {
value: {
default: 0
},
},
data() {
return {
currentValue: 1
};
},
watch: {
value(value) {
console.log('value change', value);
let newVal = Number(value);
if (isNaN(newVal)) return;
this.currentValue = newVal;
this.$emit('input', newVal);
}
},
computed: {
},
methods: {
setCurrentValue(newVal) {
const oldVal = this.currentValue;
if (oldVal === newVal) return;
this.$emit('change', newVal, oldVal);
this.$emit('input', newVal);
this.currentValue = newVal;
},
handleInput(e) {
const value = e.target.value;
const newVal = Number(value);
if (!isNaN(newVal)) {
this.setCurrentValue(newVal);
}
}
}
};
</script>
The main code is this.value = parseInt(val).
The initial value is 1. I type 1.1 in input. Then I get changes in watching.
After I set this.value, the value watching function isn't called. There is no log.
What's more strange, if I change this.value = parseInt(val) to this.value = 2, it works once, but the second time I change the value, it doesn`t work.
Code is here: https://github.com/lext-7/vue-watch-component-demo.
Run it, and type 1.1 in that input. You will find that it won't become 1.