I started to work recently on a VueJS project (first time with that framework) and I face a problem.
I have an object (called "propObject") defined in a mother component. That propObject gets its value via a webservice, called in a beforeRouteEnter method in that mother component.
I have to pass this propObject to a child component so I can display what's inside (a "libelle" attribute, among other things). I tried to do it using v-bind and props but I didn't manage to make it work.
Here is my code :
Mother.vue
<template>
<div class="row justify-content-center">
<b-container>
<b-row>
{{propObject.libelle}}
<b-col> <cpm-child :prop-object="propObject"/></b-col>
[...]
</b-row>
</b-container>
</template>
<script lang="ts" src="./mother.component.ts"></script>
Mother.component
#Component({
components: {
'cpm-child': Child,
},
})
export default class Mother extends Vue {
#Inject('propObjectService') private propObjectService: () => propObjectService;
public propObject: IPropObjectClass = new PropObjectClass();
beforeRouteEnter(to, from, next) {
next(vm => {
if (to.params.propObjectId) {
vm.load(to.params.propObjectId);
}
});
}
public load(propObjectId: string): void {
this.propObjectService()
.find(propObjectId)
.then(res => {
this.propObject = res;
});
}
}
Child.vue
<template>
<div>
<span>
{{propObject.libelle}}
[...]
</span>
</div>
</template>
<script lang="ts" src="./child.component.ts"></script>
Child.component
export default class Child extends Vue {
props: {
propObject: IPropObjectClass,
}
}
propObject.model.ts
export interface IPropObjectClass {
code?: string;
libelle?: string;
[...]
}
export class PropObjectClass implements IPropObjectClass {
constructor(
public code?: string,
public libelle?: string,
[...]
) {}
}
My goal is to display the {{propObject.libelle}} in the child vue. In the Google Chrome's console, propObject is considered "undefined".
Last information : {{propObject.libelle}} is displayed correctly in the mother vue after a few seconds, so the propObjectService works as intended.
So far, nothing I tried worked, so any help would be greatly appreciated. If you need further clarification, don't hesitate to ask.
I created a sample with Vue 2 / Vue CLI showing a standard way of initializing a prop with data before rendering the child. You should be able to port it to your app.
The main takeaways are that you can call your data service in the parent (Mother) created() lifecycle hook. And by using the v-if directive, you child will not be rendered until the prop has been updated with data from the service call.
Parent.vue
<template>
<div class="parent">
<h4>Parent</h4>
<hr>
<child v-if="user" :userProp="user"/>
</div>
</template>
<script>
import axios from 'axios'
import Child from './Child.vue'
export default {
components: {
Child
},
data() {
return {
user: null
}
},
methods: {
getUser() {
axios.get('https://jsonplaceholder.typicode.com/users/1')
.then(response => this.user = response.data)
.catch(error => console.log(error));
}
},
created() {
this.getUser();
}
}
</script>
Child.vue
<template>
<div class="child">
<h5>Child</h5>
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-3 font-weight-bold">ID</div>
<div class="col-md-5">{{ user.id }}</div>
</div>
<div class="row">
<div class="col-md-3 font-weight-bold">NAME</div>
<div class="col-md-5">{{ user.name }}</div>
</div>
<div class="row">
<div class="col-md-3 font-weight-bold">USER NAME</div>
<div class="col-md-5">{{ user.username }}</div>
</div>
<div class="row">
<div class="col-md-3 font-weight-bold">EMAIL</div>
<div class="col-md-5">{{ user.email }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
userProp: {
type: Object,
required: true
}
},
data() {
return {
user: this.userProp
}
}
}
</script>
Related
I have a JobComponent.vue component where I fetch data from a VUEX Store. This component is used on two separate pages, first page Home.vue and second page AllJobs.vue.
In AllJobs.vue I used JobComponent.vue and everything is works fine, it's rendering all the jobs, but, here comes the problem...
In Home.vue I want to render only the last 5 jobs, so in store I make a getter that slice me only the latest 5 jobs.
How can I use this latestJobs from getters on the same component?
When I import the component in Home.vue page I can't use another v-for direct on the component...
here you can see my project structure and files
Home.vue
<template>
<div class="cards-container">
<JobComponent />
</div>
</template>
JobComponent.vue
<template>
<div v-for="job in allJobs" :key="job.id" class="card">
<div class="position">{{ job.position }}</div>
<div class="department">{{ job.department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ job.location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="deleteJob(job.id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
methods: {
...mapActions(['fetchJobs', 'deleteJob']),
},
computed: mapGetters(['allJobs']),
created() {
this.fetchJobs();
}
}
</script>
store.js (vuex)
const getters = {
allJobs: (state) => state.jobs,
latestJobs: (state) => {
const response = state.jobs.slice(0, 5);
return response;
}
};
Your component should be as independent as possible from the store. It's role is to display what ever is provided so it could be reused as you want, using props :
JobComponent.vue
<template>
<div class="card">
<div class="position">{{ position }}</div>
<div class="department">{{ department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="$emit('deleteJob', id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
export default {
props: {
id: string,
position: string,
department: string,
location: string
}
}
</script>
In this component you only display the provided data, and leave the responsibility of the parent component to choose how many components to display.
Home.vue
<template>
<div class="cards-container">
<JobComponent v-for="job in jobs" :key="job.id" :id="job.id" :position="job.position" :department="job.department" :location="job.location" #delete-job="deleteJob" />
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('fetchJobs')
},
computed: {
jobs() {
return this.$store.getters['latestJobs'] // Or allJobs, just make sure your getter returns an array even if no jobs are loaded yet.
}
},
methods: {
deleteJob() {
// Your logic for job delete
}
}
}
</script>
I want to reload one of my components when an other component changes (for example I send a put with axios)
I tried it with eventbus but I got this error:
handler.apply is not a function
In the component where I want to trigger:
EventBus.$emit('compose-reload', Math.random()*100);
Where I want to be triggered:
<template>
<div class="">
<div class="">
<div class="columns">
<div class="column is-one-fifth">
<div class="box">
<Menu></Menu>
</div>
</div>
<div class="column is-four-fifth">
<div class="box">
<router-view :key="key"></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Menu from './includes/Menu'
import EventBus from '../../../event-bus';
export default {
components: {
Menu,
},
data() {
return {
key: null
}
},
mounted(){
EventBus.$on('compose-reload', this.key);
},
created(){
this.key = Math.random()*100
}
}
</script>
EventBus.$on expects a handler function as a second argument but the variable this.key is passed in, hence the error.
You should change this :
mounted(){
EventBus.$on('compose-reload', this.key);
}
To this :
mounted(){
EventBus.$on('compose-reload', key => {
this.key = key;
});
}
I'm a VueJS beginner and i'm struggling to understand some component logic.
If i have my component (simplified for clarity) :
Vue.component('nav-bar', {
template: '<nav [some code] ></nav>'
}
This component represent the whole navigation bar of my page.
In my HTML file, how can i insert code inside the component?
Something like:
<nav-bar>
<button></button>
...
</nav-bar>
Could you please tell me if it is the right way to do it?
There are at least three options I can think of:
Using ref, or
Slot props with scoped slots, or
provide/inject.
1. Example with ref
Vue.component('NavBar', {
template: `
<nav>
<slot></slot>
</nav>
`,
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar ref="navbar">
<button #click="$refs.navbar.run()">Run with refs</button>
</nav-bar>
</div>
2. With Scoped <slot>
Vue.component('NavBar', {
template: `
<nav>
<slot v-bind="$options.methods"></slot>
</nav>
`,
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar>
<template #default="methods">
<button #click="methods.run">Run with slot props</button>
</template>
</nav-bar>
</div>
3. With provide and inject
Vue.component('NavBar', {
template: `
<nav>
<slot></slot>
</nav>
`,
provide() {
const props = {
...this.$options.methods,
// The rest of props you'd like passed down to the child components.
};
return props;
},
methods: {
run() {
console.log('Parent\'s method invoked.');
}
}
});
// In order to "receive" or `inject` the parent props,
// the child(ren) needs to be a component itself.
Vue.component('Child', {
template: `
<button #click="run">
<slot></slot>
</button>
`,
// Inject anything `provided` by the direct parent
// This could also be `data` or `props`, etc.
inject: ['run']
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<nav-bar>
<template>
<child>Run with injected method</child>
</template>
</nav-bar>
</div>
Here is some code that uses $set() to add a new reactive prop to the model. It works fine.
<template>
<div id="app">
<div>
Prop1: {{ x.prop1 }}
</div>
<div>
<input type="button" value="Go" #click="go()">
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
x: {}
};
},
methods: {
go() {
this.$set(this.x, 'prop1', 'yay');
}
}
};
</script>
Now, if I remove the x root property and try to add prop1 directly to the this it doesn't work.
<template>
<div id="app">
<div>
Prop1: {{ prop1 }}
</div>
<div>
<input type="button" value="Go" #click="go()">
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
};
},
methods: {
go() {
this.$set(this, 'prop1', 'yay');
}
}
};
</script>
I get that you should do this kind of thing, but I can't figure out why it doesn't work.
As stated in the docs:
The target object cannot be a Vue instance, or the root data object of a Vue instance.
It's a technical limitation.
Using VUE 2.0 and VUEX I am a bit confused about how to pass data from parent to child.
<template>
<div id="app" class="container">
<div class="card" v-for="(triad, index) in triads">
<div class="row">
<div class="col-sm-4">
<people />
</div>
<div class="col-sm-4">
<places />
</div>
<div class="col-sm-4">
<equipment />
</div>
</div>
</div>
</div>
</template>
I am looping through an array named "triads":
state: {
triads: [
{
people: [],
places: [],
equipment: []
}
]
}
I want to send the triad variable to <people />, <places /> and <equipment />.
How do I get the content from the parent template to the child template? Thank you.
You just need to add PROP to your child components and then bind data.
E.g. <people :yourProp='triad'>
In your child components (as per docs: https://v2.vuejs.org/v2/guide/components.html#Props):
Vue.component('people', {
// declare the props
props: ['yourProp'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<span>{{ yourProp }}</span>'
})
you do not need vuex to just pass data. You need Vuex to share states between components (bi-directional).
you can pass the properties down by the means of props
<template>
<div id="app" class="container">
<div class="card" v-for="(triad, index) in triads">
<div class="row">
<div class="col-sm-4">
<people :someproperty='triad'></people>
</div>
<div class="col-sm-4">
<places :someproperty='triad'></places>
</div>
<div class="col-sm-4">
<equipment :someproperty='triad'></equipement>
</div>
</div>
</div>
</div>
</template>
and inside each of these children components, mention the props like so:
export default {
props: ['someproperty']
}
I think your parent component too doesnt have access to the property directly, so you could use mapGetters in the parent to have access to it, at the same time, it follows that your state too has a getter.
state: {
triads: [
{
people: [],
places: [],
equipment: []
}
]
},
getters: {
getTriads: (state) => {
return state.triads
}
}
Now, you can use mapGetters in your parent:
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
'triads': 'getTriads'
})
}
}
If that is too much of a setup, just try this
export default {
computed: {
triads () {
/**
* You could also try, return this.$store.state.triads
* but DONT do that, that defeats the purpose of using vuex.
*/
return this.$store.getters.getTriads
}
}
}