How to pass data between components when using Vue-Router - vue.js

How do I pass data from one component to another when using Vue router?
I'm building a simple CRUD app that have different components.
My conponents are:
App.vue - where I render the router-view
Contacts.vue - where I have the array of objects of contacts
ContactItem.vue - handle how the contact is displayed (gets contact as a prop from contact.vue
AddContact.vue - add new contact
EditContact.vue - edit selected contact
On the AddContact component, I have a form the user fills and then clicks on the submit button to add the form to the main component in Contacts.vue but when I emit an event and call it on the Contacts.vue component, it doesn't work. I get no output but from devtools I can see the event was triggered from AddContact.vue component.
Here is the Github link
<!-- App.vue -->
<template>
<div>
<Navbar />
<div class="container">
<router-view #add-contact="addContact" />
</div>
</div>
</template>
<script>
import Navbar from "./components/layout/Navbar";
export default {
components: {
Navbar
}
};
</script>
<!-- Contacts.vue -->
<template>
<div>
<div v-for="(contact) in contacts" :key="contact.id">
<ContactItem :contact="contact" />
</div>
</div>
</template>
<script>
import ContactItem from "./ContactItem";
export default {
components: {
ContactItem
},
data() {
return {
contacts: [
{
id: 1,
name: "John Doe",
email: "jdoe#gmail.com",
phone: "55-55-55"
},
{
id: 2,
name: "Karen Smith",
email: "karen#gmail.com",
phone: "222-222-222"
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "099-099-099"
}
]
};
},
methods: {
addContact(newContact) {
console.log(newContact);
this.contacts = [...this.contacts, newContacts];
}
}
};
</script>
<!-- AddContact.vue -->
<template>
<div>
<div class="card mb-3">
<div class="card-header">Add Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
export default {
components: {
TextInputGroup
},
data() {
return {
name: "",
email: "",
phone: ""
};
},
methods: {
addContact() {
const newContact = {
name: this.name,
email: this.email,
phone: this.phone
};
this.$emit("add-contact", newContact);
}
}
};
</script>

Well there are more ways to send data from component to component:
You could create a new Vue instance in your main.js file and call it eventBus
export const eventBus = new Vue();
After that you can import this bus wherever you need it:
import { eventBus } from "main.js"
Then you can send events over this global bus:
eventBus.$emit("add-contact", newContact);
At the other component you need to import this bus again and listen to this event in your "created" lifecycle:
created(){
eventBus.$on("add-contact", function (value){
console.log(value)
})
}
The other way is to store it centralized in vuex state. With "created" you can call this data. Created gets executed after your vue instance is created

Related

How to pass class attribute to child component element

how can I pass class attribute from parent to child component element?
Look here:
https://codesandbox.io/s/pedantic-shamir-1wuuv
I'm adding class to the Component "Input Field"
And my goal is that the 'custom-class' will be implemented on the 'input' element in the child component
But just still using the class attribute, and not setting a new prop like "customClass" and accept it in the props of the component
Thanks!
This depends on the template structure of your ChildComponent
Your Parent Component can look like this:
<div id="app">
<InputField class="anyClass" />
</div>
If your Child looks like this:
<template>
<input ... />
</template
Because if you have only 1 root Element in your template this will inherit the classes given automatically.
if your Template e.g. looks like this: (only available in vue3)
<template>
<input v-bind="$attrs" />
<span> hi </span>
</template
You will need the v-bind="$attrs" so Vue knows where to put the attributes to. Another Solution would be giving classes as props and assigning it to the element with :class="classes"
The pattern for the customs form component in Vue 2 where the props go to a child element looks something like this.
<template>
<div class="input-field">
<label v-if="label">{{ label }}</label>
<input
:value="value"
:class="inputClass"
#input="updateValue"
v-bind="$attrs"
v-on="listeners"
/>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: "",
},
value: [String, Number],
inputClass: {
type: String,
default: "",
},
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
};
},
},
methods: {
updateValue(event) {
this.$emit("input", event.target.value);
},
},
};
</script>
The usage of those components could look something like this.
```html
<template>
<div id="app">
<InputField
v-model="name"
label="What is your name?"
type="text"
class="custom"
inputClass="input-custom"
/>
<p>{{ name }}</p>
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
data() {
return {
name: "",
};
},
};
</script>
A demo is available here
https://codesandbox.io/s/vue-2-custom-input-field-4vldv?file=/src/components/InputField.vue
You need to use vue props . Like this:
child component:
<template>
<div>
<input :class="className" type="text" value="Test" v-bind="$attrs" />
</div>
</template>
<script>
export default {
props:{
className:{
type:String,
default:'',
}
}
};
</script>
Parent component:
<div id="app">
<InputField :class-name="'custom-class'" />
</div>
Since you're using vue2 the v-bind=$attrs is being hooked to the root element of your component the <div> tag. Check the docs. You can put this wrapper on the parent element if you need it or just get rid of it.
Here is a working example
Another approach
There is also the idea of taking the classes from the parent and passing it to the child component with a ref after the component is mounted
Parent Element:
<template>
<div id="app">
<InputField class="custom-class" ref="inputField" />
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
mounted() {
const inputField = this.$refs.inputField;
const classes = inputField.$el.getAttribute("class");
inputField.setClasses(classes);
},
};
</script>
Child Element:
<template>
<div>
<input type="text" value="Test" :class="classes" />
</div>
</template>
<script>
export default {
data() {
return {
classes: "",
};
},
methods: {
setClasses: function (classes) {
this.classes = classes;
},
},
};
</script>
Here a working example
In Vue2, the child's root element receives the classes.
If you need to pass them to a specific element of your child component, you can read those classes from the root and set them to a specific element.
Example of a vue child component:
<template>
<div ref="root">
<img ref="img" v-bind="$attrs" v-on="$listeners" :class="classes"/>
</div>
</template>
export default {
data() {
return {
classes: null,
}
},
mounted() {
this.classes = this.$refs.root.getAttribute("class")
this.$refs.root.removeAttribute("class")
},
}
Pretty much it.

How to display many times the same component with vuex link inside

I'm new to Vue and Vuex.
I made a component with radio button, and this component is two-way bind with the store.
Now that the component is working, i would like to use it for multiple item by calling it several time, and indicates to it which item of the vuex store it should be bound to. In this way, i'll be able to loop of the array containing all item on which apply the radio button modification.
In my store, I have this array :
state {
[...other data...]
arrayX {
item1: { name : 'a', value : 'nok'}
item2: { name : 'b', value : 'ok}},
[...other data...]
This array is bind with v-model inside the component :
<template>
<div class="radio">
<input type="radio" name="a" value="nok" v-model="arrayX.item1.value" id="nok"/>
<label for="nok"> Nok </label>
</div>
<div class="radio">
<input type="radio" name="a" value="" v-model="arrayX.item1.value" id="empty"/>
<label for="empty">None</label>
</div>
<div class="radio">
<input type="radio" name="a" value="ok_but" v-model="arrayX.item1.value" id="ok_but"/>
<label for="ok_but">Ok but</label>
</div>
<div class="radio">
<input type="radio" name="a" value="ok" v-model="arrayX.item1.value" id="ok"/>
<label for="ok">Ok</label>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields';
export default {
name: 'SwitchColors',
computed:{
...mapFields(['arrayX' ])
}
}
</script>
I created an example scenario in my Vue 2 CLI sandbox app. Each radio button component is bound to the Vuex store array element through a computed property. I pass the store array index value as a prop from the parent to the radio component.
RadioButtonVuex.vue
<template>
<div class="radio-button-vuex">
<hr>
<div class="row">
<div class="col-md-6">
<input type="radio" value="One" v-model="picked">
<label>One</label>
<br>
<input type="radio" value="Two" v-model="picked">
<label>Two</label>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
pickedIndex: {
type: Number,
required: true
}
},
computed: {
picked: {
get() {
return this.$store.state.radioSelections[this.pickedIndex].picked;
},
set(newValue) {
this.$store.commit('updateSelection', { index: this.pickedIndex, newValue: newValue });
}
}
},
}
</script>
<style scoped>
label {
margin-left: 0.5rem;
}
</style>
Parent.vue
<template>
<div class="parent">
<h3>Parent.vue</h3>
<radio-button-vuex :pickedIndex="0" />
<radio-button-vuex :pickedIndex="1" />
</div>
</template>
<script>
import RadioButtonVuex from './RadioButtonVuex.vue'
export default {
components: {
RadioButtonVuex
}
}
</script>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
radioSelections: [
{
picked: 'One'
},
{
picked: 'Two'
}
]
},
mutations: {
updateSelection(state, payload) {
state.radioSelections[payload.index].picked = payload.newValue;
}
}
})

It's possible to pass an object which has some methods to the child component in the Vue 3 JS

Parent component has the method "startMethods" which just also has some other method "onDecrementStart ". OnDecrementStart method just only call Alert.
Under this line, added a code example but that code didn't work.
<template>
<div class="parent">
<div class="main">
<img alt="Vue logo" src="../assets/logo.png">
<SettingsBoard :max="maxValue" :start="startValue"
:startInc="startMethods"
/>
</div>
</div>
</template>
<script>
import SettingsBoard from "#/components/SettingsBoard";
export default {
name: "Main",
components: {
SettingsBoard
},
methods: {
startMethods: {
onDecrementStart () {
alert('Decrement')
},
onIncrementStart () {
alert('Incriment')
}
},
}
}
</script>
SettingsBoard component
<template>
<div class="container">
<label>
<button :#click="startInc.onDecrementStart()">-</button>
</label>
</div>
</template>
<script>
export default {
name: "SettingsBoard",
props: {
startInc: Object
},
}
</script>
I want to get like that if it's possible.
<template>
<div class="container">
<label>
<button :#click="startInc.onDecrementStart()">-</button>
<button :#click="startInc.onIncrementtStart()">+</button>
</label>
</div>
</template>
<script>
export default {
name: "SettingsBoard",
props: {
startInc: Object
},
}
</script>
To run a method in the parent component you should emit a custom event which has the parent method as handler :
<label>
<button #click="$emit('decrement')">-</button>
<button #click="$emit('increment')">+</button>
</label>
in parent component :
<div>
<SettingsBoard
#decrement="onDecrementStart"
#increment="onIncrementStart"
/>
...
methods: {
onDecrementStart() {
this.count--;
},
onIncrementStart() {
this.count++;
},
},
LIVE EXAMPLE

How to pass form data from parent component to child?

I am trying to learn some vuejs and am struggling to understand how to pass data from a parent component to its child component. I know more is required but i'm not sure which way to go. How do pass the name in an input field in the parent component when the submit button is pressed to display in the child component?
I have tried using v-model because from what i have read and understand it is supposed to do what i need but it updates it without me even needing to press the button.
//Parent component
<template>
<div id="app">
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<Name lname="lname" fname="fname"></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit() {
submittedFname = fname,
submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
I am expecting to display the full name on the child component when the button is pressed but instead it is displayed as i am typing it.
//Parent component
<template>
<div id="app">
<form>
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
</form>
<button #click="handleSubmit(fname,lname)">submit</button>
<Name :submittedFname="submittedFname" :submittedLname="submittedLname" ></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit(fname,lname) {
this.submittedFname = fname,
this.submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
in case I forgot some things here are screenshots:
Parent component
child component
v-model means that the fname and lname instance data properties are updated each time the value of their respective input elements changes (it uses the input event behind the scenes). You then pass fname and lname directly as props to the child component. These props are reactive so it behaves as you see and the name is updated as you type.
To only change the name when submit is pressed, you can do this:
Add 2 more data properties in the parent component (e.g. submittedfname and submittedlname)
Add an #submit event listener on the form that copies the values from fname and lname to submittedfname and submittedlname
Use submittedfname and submittedlname as props for the child component.
Working code:
//Parent component
Vue.component('app', {
template: `
<div>
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<name-comp :submittedFname="submittedFname" :submittedLname="submittedLname"></Name>
</div>`,
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
methods: {
handleSubmit() {
this.submittedFname = this.fname;
this.submittedLname = this.lname;
}
}
});
//child component
Vue.component('name-comp', {
template: `
<div>
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>`,
props: {
submittedFname: String,
submittedLname: String
}
});
var vapp = new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<app />
</div>
You were missing ":" in front of your props given to the Name component. Also you didn't use this like in this.lname.

How to filter data from a parent component in a child component?

I have a component who show a list of data and I want to filter this list with different filters who are in a child component
I managed to do it with "computed" but only when I put everything in the parent component.
Test.vue
<template>
<div>
<filtres />
<garage v-for="g in garages" v-bind:gar="g" :key="g.id" />
</div>
</template>
<script>
export default {
name: 'test',
components: {
simplecomposant,
garage,
filtres
},
data(){
return{
garages: [],
}
},
mounted(){
var self = this;
axios.get('FETCHAPI').then(function (response)
{
self.garages = response.data.datas;
});
},
}
</script>
Filtres.vue
<template>
<div>
<label><input type="radio" v-model="selectedCity" value="All" /> All</label>
<label><input type="radio" v-model="selectedCity" value="1" /> City 1</label>
<label><input type="radio" v-model="selectedCity" value="2" />City 2</label>
</div>
</template>
<script>
export default {
name : 'filtres',
data(){
return{
selectedCity : "All"
}
},
}
</script>
garage.vue
<template>
<li class="aeris-simple-li">{{gar.name}}</span></li>
</template>
<script>
export default {
name : 'garage',
props: {
gar: {
type: Object
}
},
}
</script>
I want that when I select a filter (all, city 1, ...) it filters the datas "garages" who are in the parent component.