How could a child component pass its value to the parent component? Here is my child component:
Javascript:
new Vue({
el: '#table-list',
data: {
tableList: ['Empty!'],
tableSelected: ""
},
methods: {
getTableList() {
axios
.get('/tables')
.then(tableList => {
this.tableList = tableList.data;
})
.catch(e => console.warn('Failed to fetch table list'));
},
selectTable(table) {
this.tableSelected = table;
}
},
mounted() {
this.getTableList();
}
});
HTML:
<div id="table-list">
<p v-for="table in tableList">
<i class="fa fa-table" aria-hidden="true"></i>
<span class="text-primary" v-on:click="selectTable(table)"> {{ table }} </span>
</p>
</div>
When on click, selectTable is called, I want to show the value in its parent component? i.e I need to pass tableSelected property to the parent component. How could I do this?
You should use vue components, specifically events mechanism for what you want to archive.
Props are for pass data from parent to a child components, and events to send messages from child component to parent.
We have learned that the parent can pass data down to the child using props, but how do we communicate back to the parent when something happens? This is where Vue’s custom event system comes in.
Please see this fiddle https://jsfiddle.net/AldoRomo88/sLo1zx5b/
I have changed your selectTable method to emit a custom event
selectTable: function(table) {
this.$emit('item-changed',table);
}
And in your parent component you just need to listen for that event
<div>
{{selectedItem}}
</div>
<table-list #item-changed="newValue => selectedItem = newValue " ></table-list>
Let me know if you need more clarification.
Here is the page that explains how children emit events to listening parents.
Here is the page on managing state.
Remember what you're aiming for, with VUE, is MVVM. You want all your state in a store, where each item of state is stored once, regardless of how many times it's referenced, and how many ways it can be updated.
Your tableSelected is an item of state. You can pass state changes up the chain if you need to, so long as they finish in a store, not in a component or a vue. But you can keep it simple: make tableSelected a property in your store, and declare it directly in the data element of components that need it. If you want to be rigorous, put a changeTableSelected() method on the store.
You need to start worrying about props and events if one component will have many instances, or if a component knows nothing about the page on which it will appear. Until that time, I would prefer using data and the store.
Related
I am quite new in vue and working on a task which is based on vue.js. I am showing my data in a component using a prop. Now I want to add a method to increment the quantity of a product.
here is my code:
<div v-for="(products, index) in products">
<mdc-layout-cell span="2" align="middle">
{{ products.product_barcode }}
</mdc-layout-cell>
<mdc-layout-cell span="2" align="middle">
{{ products.product_quantity}}
</mdc-layout-cell>
<i class="mdc-icon-toggle material-icons float-left"
aria-pressed="false"
v-on:click="incrementItem(index)">
add
</div>
here is my JS:
export default {
props: [
'products',
],
methods: {
incrementItem(index) {
let item = this.products[index];
this.products[index].product_quantity =
this.products[index].product_quantity + 1;
console.log(this.products[index].product_quantity);
},
}
I can see the incremented value in the console, but the value is not increasing in the respective row. How could I increment the value of product_quantity? Any help would be highly appreciable
You can assign props value to a variable. Then use it child component.
export default {
props: {
products: Array,
},
data(){
return {
newProducts: this.products,
}
}
}
You should change props structure in child component. use newProducts in child component and if you update parent props data you should use emit for child to parent communication.
For further parent child communication you can follow this tutorial:
https://www.bdtunnel.com/2018/02/vue-js-component-communication-complete.html
First, in terms of vue flow, remember never mutating directly props. You should mutate the data in the parent component instead. To do this, the recommendation is to create a copy of props in children data so when click at the button, the children data change -> the parents data change, which makes the children props also change. There are many ways to do this. I dont have you parents component code so i make a generic code below, you can follow:
using sync
in parents components
<parent :products.sync=products />
in children components methods:
data() {
return {
productListInChildren: this.products; // pass props to children inner data
}
},
methods: {
incrementItem(index) {
//do modification in productListInChildren
let item = this.productListInChildren[index];
this.productListInChildren[index].product_quantity =
this.productListInChildren[index].product_quantity + 1;
// update it back to parents
this.$emit('update:products', this.productListInChildren)
}
}
<div v-for="(products, index) in productListInChildren"> // use productListInChildren instead
<mdc-layout-cell span="2" align="middle">
{{ products.product_barcode }}
</mdc-layout-cell>
<mdc-layout-cell span="2" align="middle">
{{ products.product_quantity}}
</mdc-layout-cell>
<i class="mdc-icon-toggle material-icons float-left"
aria-pressed="false"
v-on:click="incrementItem(index)">
add </i>
</div>
Second: in terms of code design, it is recommended in your case, the children should only handle the logic of display (dumb component). Logic of changing data should be moved to parents (like a controller). If that is the case. you can create a method in parents component and add the increment logic:
parents components
<parent :products=products #increment="incrementInParent"/>
methods: {
incrementInParent() {// move the logic here}
}
children components
methods: {
incrementItem(index) {
// call the methods in parents
this.$emit("increment");
}
}
First of all, you should never mutate props in the child component. You should use event communication pattern for child and parent communication.
While changing a particular value inside an array in particular index in an array or updating a property in an object the view does not react to these changes due to Object and Array Caveats in Vue.
You can read it here
https://v2.vuejs.org/v2/guide/reactivity.html
You can use $set to make the template react to changes in Object and Array.
Here is my sandbox URL which answers your question
started with VueJs for the first time
yesterday and now I'm stuck..
I have a parent component who has child items that also has a child inside them (I call them grandchildren). I want to fetch data from all the grandchildren when i click a button in the parent but i can't figure out how.
In my mind a want to call an event from parent to to all the grandchildrens that they should store their data to vuex store. Is this possible somehow or is there another way to do this?
// Data
blocks = [
{
id: 1,
type: 'HeadingBlock',
title: 'Hello',
color: 'blue'
},
{
id: 2,
type: 'ImageBlock',
image_id: 2
}
];
// App.js
<ContentBlocks :blocks="blocks" / >
// ContentBlock.vue
<ContentBlockItem v-for="(block, index) in blocks" :component="block.type" ... />
// ContentBlockItem.vue
<component :is="component" :block="block" /> // Grandchild
// component aka the grandchild (eg. HeadingBlock.vue)
data() {
return {
title: 'Hello - I want save the changed data for this heading',
color: 'blue'
}
}
So, the only call to action happens in the parent by a "save"-button. And i want as little logic in grandchildren as possible (to make it easy to create new ones, like a "ParagraphBlock").
Thx in advance
It is possible to emit() a global Event that all your components subscribe to - however it seems rather impractical. (e.g. this.$root.emit('saveData') in parent; in all children to listen to it: this.$root.on('saveData'))
A more practical approach is to store all your component data in the store in the first place. And have each component retrieve its state from the store. (e.g. in a computed property: title() { return this.$store.myComponent.title }.
The trick here is obviously to set all your store-data correctly (e.g. with componentIDs to match it correctly). To do this you need to be aware, that vuex does not support maps or sets. Also, you have to set each property/element individually - you cannot set nested structures in one go bu thave to do it recursively. Hereby Arrays have to be filled with native array methods (push(), splice()) and Object properties have to be set with Vue.set(object, key, value).
For accessing data between parent and child component you can use one of the best features of vue is vuex store. It's really helpful when you want to pass data to child component and update that data in child and again pass back to parent without the use of props and event emit.
Here is a link you can follow for your web application
https://medium.com/dailyjs/mastering-vuex-zero-to-hero-e0ca1f421d45
https://medium.com/vue-mastery/vuex-explained-visually-f17c8c76d6c4
I hope this will help you.
ive seen afew answers that sort of answer my question but not fully, so let me explain what I want to do.
We use a global #app div within the layout of our website, which is a Laravel project. So all pages will be the same main Vue instance, due to this i'm separating key functionality into components.
So, the first example is just a simple Tab component, this either separates any children into tabs, or accepts some data which the single child component then renders.
So below i'm injecting some data from another component, this ajax component literally just does an ajax call, and makes the data available within it's slot.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
#{{ row['example' }}
</div>
</div>
</tabs>
</div>
</ajax>
Now this is all well and good, to a point, but this falls down with the below code. This contains a component which will allow the used to drag and drop elements, it re-arranges them by literally moving the data around and letting Vue handle the DOM changes.
This will of course work fine within it's own data which you have injected in, but when you change the data within the component below this then clears this child component.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
<draggable :injected="row">
<div>
<div v-for="item">
#{{ item }}
</div>
</div>
</draggable>
</div>
</div>
</tabs>
</div>
</ajax>
I need to find a way to make any changes to this data apply to the parent data, rather than the data passed into the child components.
What is the best practice to do this!?
Edit 1
Basically, I need any child component's manipulate the data within the ajax component. The children within ajax could change, or there could be more, so I just need them all to do this without knowing what order or where they are.
It is hard to come up with specifics on this one, but I am going to try to put you in the right direction. There are three ways to share data between components.
1) Passing down data via props, emitting data up via custom events
The passing down of data via props is a one-way street between the parent and child components. Rerendering the parent component will also re-render the child and data will be reset to the original state. See VueJS: Change data within child component and update parent's data.
2) Using a global event-bus
Here you create an event bus and use this to emit the data to different components. All components can subscribe to updates from the event bus and update their local state accordingly. You initiate an event bus like this:
import Vue from 'vue';
export const EventBus = new Vue();
You send events like this:
import { EventBus } from './eventbus.js'
EventBus.$emit('myAwsomeEvent', payload)
And you subscribe to events like this:
import { EventBus } from './eventbus.js'
EventBus.$on('myAwsomeEvent', () => {
console.log('event received)
})
You still need to manage state in the components individually. This is a good start with an Event bus: https://alligator.io/vuejs/global-event-bus/
3) Using Vuex
Using Vuex extracts the component state into the Vuex store. Here you can store global state and mutate this state by committing mutations. You can even do this asynchonously by using actions. I think this is what you need, because your global state is external to any components you might use.
export const state = () => ({
resultOfAjaxCall: {}
})
export const mutations = {
updateAjax (state, payload) {
state.resultOfAjaxCall = payload
}
}
export const actions= {
callAjax ({commit}) {
const ajax = awaitAjax
commit('updateAjax', ajax)
}
}
Using vuex you keep your ajax results separated from your components structure. You can then populate your state with the ajax results and mutate the state from your individual components. This way, it doesn't matter whether you recall ajax, or destroy components since the state will always be there. I think this is what you need. More info on Vuex here: https://vuex.vuejs.org/
Asking for best practice or suggestion how to do it better:
I have 1 global reusable component <MainMenu> inside that component I'm doing XHR request to get menu items.
So if I place <MainMenu> in header and footer XHR will be sent 2 times.
I can also go with props to get menu items in main parent component and pass menu items to <MainMenu> like:
<MainMenu :items="items">
Bet that means I cant quickly reuse it in another project, I will need pass props to it.
And another way is to use state, thats basically same as props.
What will be best option for such use case?
If you don't want to instantiate a new component, but have your main menu in many places you can use ref="menu" which will allow you to access it's innerHTML or outerHTML. I've created an example here to which you can refer.
<div id="app">
<main-menu ref="menu" />
<div v-html="menuHTML"></div>
</div>
refs aren't reactive so if you used v-html="$refs.menu.$el.outerHTML" it wouldn't work since refs are still undefined when the component is created. In order to display it properly you would have to create a property that keeps main menu's HTML and set it in mounted hook:
data() {
return {
menuHTML: ''
}
},
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}
This lets you display the menu multiple times without creating new components but it still doesn't change the fact that it's not reactive.
In the example, menu elements are kept in items array. If the objects in items array were to be changed, those changes would be reflected in the main component, but it's clones would remain unchanged. In the example I add class "red" to items after two seconds pass.
To make it work so that changes are reflected in cloned elements you need to add a watcher that observes the changes in items array and updates menuHTML when any change is noticed:
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
this.$watch(
() => {
return this.$refs.menu.items
},
(val) => {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}, {
deep: true
}
)
}
You can also watch for changes in any data property with:
this.$refs.menu._data
With this you don't need to pass props to your main menu component nor implement any changes to it, but this solution still requires some additional logic to be implemented in it's parent component.
This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0