How can I create a parent component that can receive and render a child component?
For example:
<Form>
<Input/>
<Input/>
<Select/>
<Button/>
<Form/>
You can use slots.
Parent.vue
<template>
<form>
<slot/> <!-- The child component will be rendered here -->
</form>
</template>
Child.vue
<template>
<div>
<input/>
<input/>
</div>
</template>
Grandparent.vue
<template>
<div>
<Parent> <!-- Parent component -->
<Child/> <!-- Pass child component to it as slot -->
</Parent>
</div>
</template>
<script>
import Parent from './Parent.vue'
import Child from './Child.vue'
components: {
Parent,
Child
}
</script>
You should import the components as follows:
import Select from "../components/Select";
import Button from "../components/Button";
import Input from "../components/Input";
And then register them in the parent as follows:
components: {
Select,
Button,
Input
},
You can send your child components to another component, say parent component, using slots.
You need to assign a name to the child components like
<Form>
<Input name="input1"/>
<Input name="input2"/>
<Select name="select"/>
<Button name="button"/>
<Form/>
Then inside the parent component, you can receive it like
<template>
<form>
<slot name="input1"/>
<slot name="input2"/>
<slot name="select"/>
<slot name="button"/>
</form>
</template>
The slots with corresponding names will be replaced with the child components.
Related
I have a wrapper component(Wrapper.vue).
<component v-for="c of comps" :key="c.componentName" :is="c.componentName">
<slot :name="c.componentName">
</slot>
</component>
const comps =
[
{
componentName: "Child1",
},
{
componentName: "Child2",
}
];
Child1.vue is something like below
<template>
<div class="hello">
<slot name="top">Child1 slot top text</slot>
</div>
</template>
Child2.vue is something like below
<template>
<div class="hello">
<slot name="top">Child2 slot top text</slot>
</div>
</template>
I want to use the wrapper like below in App.vue
<Wrapper>
<template slot="slot1">
<template slot="top">Inner Content1</template>
</template>
<template slot="slot2">
<template slot="top">Inner Content2</template>
</template>
</Wrapper>
CodeSandbox Link:- https://codesandbox.io/s/mystifying-burnell-ir8un
It is showing default text from Child1 and Child2 not from App.vue
What should be done to fix it?
I changed some code and this is the result. The changes I made:
you had nested the <template> tag inside App.vue.
the wrapper itself needed to have a template for slot named 'top' and inside this template I added a slot with the same name of the component
https://codesandbox.io/s/lucid-smoke-uzgn7?file=/src/App.vue
I want to get a third party library component, add one element more and use this third party with the same way as always.
Example:
<third-party foo="bar" john="doe" propsFromOriginalLibrary="prop">
<template v-slot:top=v-slot:top={ props: test }>
Some text on third-party component slot
</template>
</third-party>
Want to code as:
<my-custom-component propsFromOriginalLibrary="prop">
<template v-slot:top={ props: test }>
Some text on third-party component slot
</template>
</my-custom-component>
And both examples work the same way. I was able to get all the props by using:
<third-party v-bind="$attrs">
but not sure about how to handle the slots
Here is how you can do it:
my-custom-component template:
<template>
<third-party v-bind="$attrs">
<template v-slot:top="slotProps">
<slot name="top" v-bind="slotProps"></slot>
</template>
</third-party>
</template>
What's happening:
Inserting the third party component and v-bind $attrs
Referencing its top slot
Custom component has a slot which is passed into third's slot
Custom slot has the same name so it can be v-slot the same way from a parent
Custom slot v-binds all 3rd party slotProps to pass out to a parent
You can use a v-for to avoid the need for hard-coding an inner template for each slot. For example if you wanted to expose two slots, top and bottom:
<template>
<third-party v-bind="$attrs">
<template v-for="slot in ['top','bottom']" v-slot:[slot]="slotProps">
<slot :name="slot" v-bind="slotProps"></slot>
</template>
</third-party>
</template>
Demo:
Vue.component('third-party', {
props: ['background'],
template: `
<div class="third" :style="{ background }">
3rd party slot:
<div class="third-slot">
<slot name="top" :props="props"></slot>
</div>
</div>
`,
data() {
return {
props: 'Third party Prop'
}
},
})
Vue.component('my-custom-component', {
template: `
<div>
<component is="third-party" v-bind="$attrs">
<template v-for="slot in ['top']" v-slot:[slot]="slotProps">
<slot :name="slot" v-bind="slotProps"></slot>
</template>
</component>
</div>
`
})
/***** APP *****/
new Vue({
el: "#app"
});
.third,.third-slot {
padding: 10px;
}
.third-slot {
background: #cccccc;
border: 1px solid #999999;
font-weight: bold;
}
<script src="https://unpkg.com/vue#2.6.12/dist/vue.js"></script>
<div id="app">
<third-party background="#eeeeff">
<template v-slot:top="{ props: test }">
Some text on third-party component slot
<div>{{ test }}</div>
</template>
</third-party>
<my-custom-component background="red">
<template v-slot:top="{ props: test }">
Some text on third-party component slot
<div>{{ test }}</div>
</template>
</my-custom-component>
</div>
Fun: You could even make the wrapped component dynamic like <component :is="thirdpartyName"> and the slot name array too; even passing this info in from outside for a fully generic wrapper. But there's no need for that here
main.js file:
import Vue from 'vue'
import App from './App.vue'
import Parent from './assets/components/frame/Parent.vue';
import LeftSide from './assets/components/frame/LeftSide.vue';
import RightSide from './assets/components/frame/RightSide.vue';
import HeaderLeft from './assets/components/header/HeaderLeft.vue';
import HeaderRight from './assets/components/header/HeaderRight.vue';
Vue.component('Parent', Parent);
Vue.component('LeftSide', LeftSide);
Vue.component('RightSide', RightSide);
Vue.component('HeaderLeft', HeaderLeft);
Vue.component('HeaderRight', HeaderRight);
new Vue({
el: '#app',
render: h => h(App)
})
Non working app.vue file, no data appears on web page. When examining the page via dev tools, the parent div is there, but it's empty. I'm expecting the LeftSide and RightSide to be nested inside of it.
<template>
<div>
<Parent>
<LeftSide>
</LeftSide>
<RightSide>
</RightSide>
</Parent>
</div>
</template>
<script>
</script>
<style>
</style>
When modified to the below (exclude 'Parent'), to have no nested components, the data (left side and right side) outputs to the web page fine.
<template>
<div>
<LeftSide>
</LeftSide>
<RightSide>
</RightSide>
</div>
</template>
<script>
</script>
<style>
</style>
Parent component below
<template>
<div class="parent">
</div>
</template>
<script>
</script>
<style>
.parent
{
display: flex;
}
</style>
I fixed it by moving LeftSide and RightSide into the Parent.vue, instead of having the LeftSide and RightSide inside the Parent in the app.vue.
As you already figured out yourself you should move those components into the Parent component.
But there are situations where you want to have a reusable component as decorator (something like a panel) and place elements inside of it. In this case you are looking for slots. Using slot you can indeed write this:
<Parent>
<LeftSide />
<RightSide />
</Parent>
And in the Parent component you would need to define where those components have to be placed using <slot></slot>
<template>
<div class="parent">
<slot></slot>
</div>
</template>
<script>
</script>
<style>
.parent
{
display: flex;
}
</style>
So I have my auth class injected into my main.js:
import {Auth} from 'auth';
import {inject} from 'aurelia-framework';
#inject(Auth)
export class App {
constructor(auth) {
this.auth = auth;
}
get isLoggedIn() { return this.auth.isLoggedIn; }
}
so in my app.html
<form>
<!-- form login elements -->
</form>
how do I make this element conditionally display based on my app getter function.
You can use if.bind to conditionally bind your DOM elements.
<form>
<div if.bind="auth.isLoggedIn">
<!--this DOM element will be bind only if auth.isLoggedIn is true-->
</div>
</form>
Optionally, you can also use show.bind but that will only hide your DOM elements. Where as if.bind will not render it on your page.
If you need to remove element completely from markup when condition is not met, you can use if.bind (as #Pratik Gajjar answered):
<template>
<div if.bind="auth.isLoggedIn">
<!--this DOM element will be bind only if auth.isLoggedIn is true-->
</div>
</template>
If you need to conditionally set display: none on element, you can use show.bind:
<template>
<div show.bind="auth.isLoggedIn">
<!--this DOM element will be shown only if auth.isLoggedIn is true-->
</div>
</template>
For details have a look at http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/cheat-sheet/6.
So I created a value converter:
export class CssdisplayValueConverter {
toView(value) {
return value ? 'none' : 'display';
}
}
Then in my app.html:
<require from='css-display'></require>
<form css="display: ${isLoggedIn() | cssdisplay}"></form>
Boom, done.
You can use if.bind and show.bind for binding an element by checking a condition
How do I get a reference to a child component of the current component from within the controller? For example:
<aura:component>
<div class="container">
<foo:myComponent />
</div>
</aura:component>
In this case I would like to have a reference to myComponent from within this component's controller.
You need to assign an aura:id to your component and then use find().
Component:
<aura:component>
<div class="container">
<foo:myComponent aura:id="myFancyComponent" />
</div>
</aura:component>
Controller:
init: function(cmp, event){
var myComponent = cmp.find("myFancyComponent");
}