VuesJS components template - vue.js

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>

Related

VUE3 use different v-for for the same component

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>

VueJS - using v-bind to pass an attribute supplied by a webservice

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>

Passing parents props to slot in Vue,js

I have in my application a DocumenContainer component which has multiple ChartContainer components. The ChartContainer has a slot in which I put various types of Charts (bar chart, Line Chart etc.). I would like to pass the data isOuput to the child component which is a slot
ChartContainer (simplified):
<template>
<div class="card-body">
<slot v-slot="isOutput"></slot>
</div>
</template>
<script>
export default {
data() {
return {
isOutput : false,
}
}
</script>
DocumentContainer:
<chart-container title="Stats Model" v-slot="slotProps" :documentId="id">
{{slotProps.isOuput}}
<v-bar-chart :docId="id"></v-bar-chart>
</chart-container>
I tried passing the isOutput to the parent (DocumentContainer) with v-slot. The problem right now is that I'm only able to print {{slotProps.isOutput}}. I would like to pass that slotProps.isOutput as a props to the <v-bar-chart> and
<v-bar-chart :isOuput="slotProps.isOutput" :docId="id"></v-bar-chart>
is giving me undefined in the bar-chart props.
Is there a simpler way than to pass the data to the parent and to the child? How can I achieve this?
I think this is something to do with the context
It will work if you use v-bind instead
<v-bar-chart v-bind="{ isOutput: slotProps.isOutput, docId: id }"></v-bar-chart>
Example code
const Component1 = {
template: `
<div>
<h2>Component 1</h2>
<button #click="isOutput = !isOutput">Toggle</button>
<slot :isOutput="isOutput"></slot>
</div>
`,
data() {
return {
isOutput: false,
}
}
};
const Component2 = {
props: ['isOutput'],
template: `
<div>
<h2>Component 2</h2>
isOutput: {{String(isOutput)}}
</div>
`
};
new Vue({
el: '#app',
components: {
Component1,
Component2
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<div id="app">
<h1>Home</h1>
<Component1>
<template v-slot="slotProps">
isOutput: {{String(slotProps.isOutput)}}
<Component2 v-bind="{ isOutput: slotProps.isOutput }">
</Component2>
</template>
</Component1>
</div>

Reusable nested VueJS components

Is it possible to declare a component inside another component in Vue.JS?
this is what i'm trying to do:
<!-- this is declared inside some-component.vue file -->
<script>
export default {
components:{
'cmptest' : {
template:'#cmptest',
props:['mprop']
}
},
data : () => ({
val:'world'
})
};
</script>
<template>
<div>
<template id="cmptest">
{{mprop}}
</template>
<cmptest mprop="hello"></cmptest>
<cmptest :mprop="val"></cmptest>
</div>
</template>
I'd like to avoid globally registering the child component if possible (with Vue.component(...))
In other words, I'd like to specify child's <template> inside the parent component file (without doing a huge line template:'entire-html-of-child-component-here')
Sure.
Like this:
https://jsfiddle.net/wostex/63t082p2/7/
<div id="app">
<app-child myprop="You"></app-child>
<app-child myprop="Me"></app-child>
<app-child myprop="World"></app-child>
</div>
<script type="text/x-template" id="app-child2">
<span style="color: red">{{ text }}</span>
</script>
<script type="text/x-template" id="app-child">
<div>{{ childData }} {{ myprop }} <app-child2 text="Again"></app-child2></div>
</script>
new Vue({
el: '#app',
components: {
'app-child': {
template: '#app-child',
props: ['myprop'],
data: function() {
return {
childData: 'Hello'
}
},
components: {
'app-child2': {
template: '#app-child2',
props: ['text']
}
}
}
}
});

VUE / VUEX: How To Pass Data From Parent Template To Child Template

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
}
}
}