Components and Sub components - vue.js

I'm new to Vue.js and I'm having a bit of trouble using components with sub-components. I have the following .vue files
app.vue
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider>
<skin></skin>
</slider>
</section>
</template>
slider.vue
<template>
<div id="slider-panel">
<h3>{{* heading}}</h3>
<div class="slider">
<slot>
Some content
</slot>
</div>
</div>
</template>
<script>
import skin from "./skin";
export default {
components: {
skin: skin
}
};
</script>
skin.vue
<template>
<div v-for="colour in colours">
<div :style="{ backgroundColor: colour }">
<img src="../assets/images/MIA.png"/>
</div>
</div>
</template>
<script>
export default {
data() {
return {
heading: "Choose Skin Tone"
};
}
};
</script>
I'm trying to load the skin sub component into the component. Everything works well except for the skin sub component as it doesn't get compiled. I do not get any compile or vue related errors though. I also wanted to be able to have several instances of the slider component like this
app.vue
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider>
<skin></skin>
</slider>
<slider>
<foo></foo>
</slider>
<slider>
<bar></bar>
</slider>
</section>
</template>
I'm not sure what I am doing wrong.

I'm not 100% sure of what you want to achieve here, but to compile a component inside a component, you need to add the child component inside the parent's template, like this:
Slider.vue (I've simplified the structure):
<template>
<div id="slider-panel">
<h3>{{* heading}}</h3>
<skin></skin>
</div>
</template>
<script>
import skin from './skin'
export default {
components : {
'skin': skin
}
}
</script>
App.vue:
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider></slider>
</section>
</template>
Actually, if you add skin in the app's template inside of adding it in the slider component template, it gets included (and rendered) assuming that its scope is app, not slider. In order to add skin inside slider scope, it needs to be added to slider's template. Check this: https://vuejs.org/guide/components.html#Compilation-Scope
Some other things:
Use a hyphen-separated name for the components, with at least 2 words: <custom-slider> instead of <slider>, for example, following the Web Components API (otherwise it might collide with current or upcoming web components).
Slots are complicated to grasp, so read this carefully: https://vuejs.org/guide/components.html#Content-Distribution-with-Slots
Good luck!
Update:
If you want the slider component to be content agnostic and be able to insert anything you want inside it, you have two options (that I can think of):
Remove all the logic from the slider component and make skin a descendant from app. Then use slots in the slider component, as follows:
Slider.vue:
<template>
<div id="slider-panel">
<h3>{{* heading}}</h3>
<slot></slot>
</div>
</template>
<script>
export default {}
</script>
App.vue:
<template>
<section>
<menu></menu>
<h1>Create Your MIA</h1>
<div id="board"></div>
<slider>
<skin></skin>
</slider>
</section>
</template>
<script>
import skin from './skin'
export default {
skin: skin
}
</script>
If you know that the slider will always have a closed set of components inside, you can use dynamic components: https://vuejs.org/guide/components.html#Dynamic-Components

After some research I found this which refers to a is= attribute that will transclude the sub-component template
so in app.vue
<slider-component>
<div is="skin-component" v-for="colour in colours"></div>
</slider-component>
and then add child components

Related

VueJS: How can I pass params into a component?

I am new to vueJS.
What I want to do is passing parameters to a component, depending on the selection of the routes. Here is my App.vue:
<template>
<div id="main">
<header>
<h1 style="color:red">{{msg}}</h1>
</header>
<div>
<aside class="sidebar">
<router-link v-for="el in this.$router.options.routes" :to="el">
{{el.name}}
</router-link>
</aside>
<SubMenu></SubMenu>
<div class="content">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
import SubMenu from './components/SubMenu.vue'
export default {
components: {
'SubMenu': SubMenu
},
data() {
return {
msg: 'Welcome to Your Vue.js App' }
}
}
</script>
<style>
#import 'style.css';
#import 'grid.css';
</style>
and the SubMenu component I would like to make dynamic:
<template>
<div>
something dynamic
</div>
</template>
How can I pass some parameters to use in the component?
thank you
Your App.vue can be like this:
<template>
<div id="main">
<header>
<h1 style="color:red">{{msg}}</h1>
</header>
<div>
<aside class="sidebar">
<router-link v-for="el in this.$router.options.routes" :to="el">
{{el.name}}
</router-link>
</aside>
<SubMenu :menuTitle="subMenuTitle"></SubMenu>
<div class="content">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
import SubMenu from './components/SubMenu.vue';
export default {
components: {
SubMenu
},
data() {
return {
subMenuTitle: "This is the sub menu",
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<style>
#import 'style.css';
#import 'grid.css';
</style>
The SubMenu.vue component could be like this:
<template>
<div>
<h2>{{ menuTitle }}</h2>
something dynamic
</div>
</template>
<script>
export default {
name: "SubMenu",
props: {
menuTitle: String,
}
}
</script>
In the SubMenu component that was used in App.vue, notice the colon that appears before the menuTitle attribute. When you do that before an attribute, the value of that attribute would be evaluated by Vue and passed to the component. You can pass literal Javascript expressions or items in your App.vue component.
In the SubMenu component, you can use the props in whatever way you can. If the prop's value is an array, you can use the v-for directive with it to create a list of items in the SubMenu.
Welcome to SO,
In Vue.js passing parameters to components is called "Props"
You can pass props to your SubMenu like below
<SubMenu :id="12345" someText="Some Text About Something" :dataArray="[1,2,3,4,5]" />
then inside your SubMenu component you can define Prop Types as below
props: ['dataArray']
or
props: {
dataArray: {
type: Array,
default: []
}
}
After that you can use the data you passed to your liking
You can also read up on this Vue Documentation regarding the Props, which has much more detailed explanations about various Props related stuff and sample code
Ok many thanks to both.
But what if I would like to pass something that depends on the voices in router-link? I mean, router-link prints a menu with 4 voices...what if I would like a behavior like this:
click on voice1 in router-link ---> pass this object ['input1', 'input2'] to SubMenu
click on voice2 in router-link ---> pass this other object ['input3', 'input4', 'input5'] to SubMenu
and so on.
thanks again :)

How can I display desired/particular chart in App.vue from Charts.vue which contains different charts in Vuejs

I am using Chartjs in order to display charts, I have created charts.vue component in which different charts are defined.
Here is my Charts.vue:
<template>
<div class="chart-containr">
<canvas ref="chart1"></canvas>
<canvas ref="chart2"></canvas>
</div>
</template>
<script>
...
</script>
Here is my App.vue:
<template>
<div class="content-1">
<charts/> <-- Here i want to display my chart 1
</div>
....
<div class="content-8">
<charts/> <-- Here i want to display my chart 2
</div>
</template>
<script>
import chart from './Charts'
exprt default{
name: 'App',
component: {chart}
....
}
As you can see in my App.vue, i have imported the Charts.vue and i have used charts as a component, but if i use it in the template part as both the charts are display one after another. Rather i want chart1 in the 1st div and chart2 in the 8th div.
Please help me in this, i want to display particular chart from the charts.vue in different part of App.vue. I tried using props but dint work out.
You could probably use a prop and interpolate the id on the component.
Charts.vue (should be renamed to Chart.vue in this way I guess)
<template>
<div class="chart-containr">
<canvas :ref="`chart${chartId}`"></canvas>
</div>
</template>
<script>
export default {
props: {
chartId: {
type: String,
default: '',
},
},
}
</script>
App.vue
<template>
<div class="content-1">
<charts chart-id="1"></charts>
</div>
<div class="content-8">
<charts chart-id="2"></charts>
</div>
</template>
There is maybe also a way with slots but props will be enough here.
Btw, you could even v-for and display the <charts> component programmaticaly.

How can i pass a parameter to a Vue component?

I just got started to Vue and i'm trying to understand some basic concepts such as conditional rendering and how to pass data from where i load the app to a component. Suppose i'm rendering a Vue component like this:
<div id="app">
<myComponent></myComponent>
</div>
Suppose myComponent looks like this:
<template>
<div>
<h1>First block</h1>
</h1>Second block</h1>
</div>
</template>
I want to be able to render First block or Second block when i load the Vue app according to a parameter i pass to the component, like:
<div id="app">
<myComponent id="first"></myComponent>
</div>
In this case i should see First block, whereas if instead of id="first" there was id="second" the output was supposed to be Second block. How can i do this?
I know it's a very basic question, but most of the sources i found explained how to do the opposite. Any kind of advice is appreciated!
In vue you could pass props (parameters) to component which defines this ones in its script like :
<template>
<div>
<h1 v-if="block==='first'">First block</h1>
</h1 v-else>Second block</h1>
</div>
</template>
<script>
export default{
props:{
block:{
type:String,
default:'first'
}
}
}
</script>
in parent component pass the prop like :
<div id="app">
<myComponent block="first"></myComponent>
</div>
or
<div id="app">
<myComponent block="second"></myComponent>
</div>

Vue.js Issue: Prop doesn't change image v-bind:src

I've been trying to pass a prop to a component, which is a URL to an image for Section component to update v-bind:src of dom img tag, but somehow the image does not show up.
I can't see what's wrong.
File: App.vue
<template>
<div id="app">
<Section img="../assets/linux.png" />
</div>
</template>
<script>
import Section from "./components/Section.vue";
export default {
name: "app",
components: {
Section
}
};
</script>
File: Section.vue
<template>
<div>
<img :src="img" />
</div>
</template>
<script>
export default {
props: {
img: String
}
};
</script>
I suspect that the issue is due to the relative path you are using. I assume that ../assets/linux.png resolves to the right image URL with respect to App.vue, but it actually needs to resolve to the right image with respect to your <Section> component.
Based on what I can tell from the code you've shared, It seems like you can solve this by updating App.vue as follows:
<template>
<div id="app">
<Section img="../../assets/linux.png" />
</div>
</template>
...
I should, however, point out that you are getting absolutely no benefit from passing the image source as a prop like this. Since it is not bound to a reactive data property in App.vue, you may as well just omit that prop altogether.

Vue.js dynamic layout renderless component layout with multiple slots

I am trying to build a dynamic layout for my application. I have two different layouts, one being DefaultLayout.vue:
<template>
<div>
<main>
<slot/>
</main>
</div>
</template>
and a second one being LayoutWithFooter.vue, with two slots:
<template>
<div>
<main>
<slot/>
</main>
<footer>
<slot name="footer"/>
</footer>
</div>
</template>
My renderless component to handle the dynamic layout looks like this:
<script>
import Vue from 'vue';
import DefaultLayout from './DefaultLayout';
import LayoutWithFooter from './LayoutWithFooter';
export default {
props: {
name: {
type: String,
required: true
}
},
created(){
this.registerComponent("DefaultLayout", DefaultLayout);
this.registerComponent("LayoutWithFooter", LayoutWithFooter);
this.$parent.$emit('update:layout', this.name);
},
methods: {
registerComponent(name, component) {
if(!Vue.options.components[name]) {
Vue.component(name, component);
}
}
},
render() {
return this.$slots.default[0];
},
}
</script>
All of this works fine for the DefaultLayout.vue but when I want to use the LayoutWithFooter.vue, it cannot handle the two slots inside it. Here's an example usage:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
Problem now is, that the "content for the footer slot" does not get rendered inside of the footer slot of the LayoutWithFooter.vue.
First of all I want you to pay an attention to defining slots level in your example. You provided this code:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
But actually your div slot="footer" does not refer to footer slot of LayoutWithFooter.vue component. It because of anyway the very first child refers to default slot. And as a result it looks like:
"You want to set content for default slot and inside this default slot you tried to set content for footer slot" - but it's two different scopes.
The right options would look like on the next example:
<template>
<layout name="LayoutWithFooter">
<!-- default slot content -->
<div>
<div>some content</div>
</div>
<!-- footer slot content -->
<div slot="footer">content for the footer slot</div>
</layout>
</template>
I prepared some example based on code and structure you've provided. There you are able to switch layouts and check out how it works and use different components slot in one layout.
Check it here:
https://codesandbox.io/s/sad-fog-zr39m
P.S. Maybe some point are not totally clear, please reply on my answer and I will try to explain more and/or provide you with more links and sources.