How to pass an imported component through props? - javascript

Is there a way I can pass a component through props to a child component from the parent without having to import it from the child?
Parent component:
<template>
<ChildComponent :component="componentName">
</template>
<script>
import componentName from '#/components/componentName.vue'
</script>
Child component:
<template>
<div>
<component :is="component">
</div>
</template>
<script>
props: {
component: Object
}
</script>
Currently, in this setup, I am getting Unknown custom event Did you register the component correctly? Error. I know it wants me to register the component in the child component but I don't want to have to import the component from the child. Is there a way to pass the import to the child through props? Is this not possible?

You are really close, the issue is in your first template:
<template>
<ChildComponent :component="componentName">
</template>
<script>
// this is not available in template scope
import componentName from '#/components/componentName.vue'
export default {
data: {
// this, however, is available!
componentName
}
}
</script>

You are mising export default.
parent.vue:
<template>
<ChildComponent :component="componentName">
</template>
<script>
import componentName from '#/components/componentName.vue'
export default {
data() {
return {
componentName
}
},
}
</script>
child.vue:
<template>
<div>
<component :is="component">
</div>
</template>
<script>
export default {
props: {
component: Object
},
}
</script>

Related

VueJS, component hierarchy how to use a component more than once in the hierarchy

I have a need to use a component more than once in the hierarchy starting with the root component.
Below is the code for the root Comp1
<template>
<alert/>
<modal/>
<button #click='showAlert()'></button>
</template>
<script>
import modal from './Modal'
export default{
components: {
modal
},
methods: {
showAlert(){
//code that shows the alert
}
}
}
</script>
Below is the code for child component modal
<template>
<grandchild/>
</template>
<script>
import grandchild from './GrandChild'
export default{
components: {
grandchild
}
}
</script>
and below is the code for grandchild
<template>
<alert/>
<button #click='showAlert()'></button>
</template>
<script>
export default{
methods: {
showAlert(){
//code that shows the alert
}
}
}
</script>
alert is a modal globally available component with big z-index value to make sure it is displayed over other components
When showAlert() in Comp1 is called the alert being displayed is the one in the modal component. Why is that happening?

Wrap received slots (vnodes) in anonymous component in Vue.js

I would like to receive two named slots slotOne and slotTwo. These are located in the child component at this.$scopedSlots.slotOne and this.$scopedSlots.slotTwo and contain vnodes. How can I wrap these slots (vnodes) in a new component so that I can conditionally render them like this:
Child Component:
<template>
<div>
<keep-alive>
<component :is="wrapperComponentContainingProperSlot"></component>
</keep-alive>
</div>
</template>
Parent Component:
<template>
<child>
<template v-slot:slotOne>
...
</template>
<template v-slot:slotTwo>
...
</template>
</child>
</template>
I'm guessing the core of this question is, how do I create a component from vnodes inside of another component?
I believe my motivation for desiring to implement this was based on the false premise that <keep-alive> is NOT destroyed when its parent is destroyed. This was not the case. Nevertheless, I did figure out how to wrap slots into their own components, both anonymous and named.
Child component:
<template>
<component :is="componentToRender"></component>
</template>
<script>
import Vue from 'vue';
export default {
computed: {
componentToRender() {
return this.showSlotOnesGlobally
? new this.SlotOneWrapperComponent(this)
: new this.SlotTwoWrapperComponent(this);
},
},
/* Assume a parent further up in the tree provided this data */
inject: {
showSlotOnesGlobally: 'showSlotOnesGlobally'
},
methods: {
SlotOneWrapperComponent(context) {
return Vue.component('SlotOneContentWrapper', {
render() {
return context.$scopedSlots.slotOne();
},
});
},
SlotTwoWrapperComponent(context) {
return Vue.component('SlotTwoContentWrapper', {
render() {
return context.$scopedSlots.slotTwo();
},
});
},
},
};
</script>
Parent component:
<template>
<child>
<template v-slot:slotOne>
...
</template>
<template v-slot:slotTwo>
...
</template>
</child>
</template>
To make them anonymous components, simply replace Vue.component('SlotOneContentWrapper', and Vue.component('SlotTwoContentWrapper', with Vue.extend(.
If anyone can offer a more concise solution, that would be wonderful.

Vue: how to use component as prop doesn't display empty component no html tag of component

I have three components:
HugeBox.vue
Box.vue
Dog.vue
HugeBox contains a Box which in turn contains a Dog:
HugeBox -> Box -> Dog
I try to pass the Dog to the Box as a prop, however it doesn't work: all that gets displayed when I open HugeBox is
Box with
while it should be
Box with Dog
HugeBox.vue:
<template>
<Box :myComponent = "Dog"/>
</template>
<script>
import Dog from '../test/Dog.vue';
import Box from '../test/Box.vue';
export default {
components: {Dog, Box}
}
</script>
Box.vue:
<template>
<div>
<p>Box with</p>
<component :is = "myComponent"/>
</div>
</template>
<script>
export default {
props: {
myComponent: {
type: [String, Object],
}
}
}
</script>
Dog.vue:
<template>
<p>Dog</p>
</template>
What am I doing wrong?
In Vue, if you need to pass markup or other components into a child component you can use slots.
Slots allow you to nest components within other components, just as you do with HTML.
HugeBox.vue:
<template>
<Box>
<Dog/>
</Box>
</template>
<script>
import Dog from '../test/Dog.vue';
import Box from '../test/Box.vue';
export default {
components: {Dog, Box}
}
</script>
Box.vue:
<template>
<div>
<p>Box with</p>
<slot></slot>
</div>
</template>
<script>
import Box from '../test/Dog.vue';
export default {
props: {
myComponent: {
type: [String, Object],
}
},
components: { Dog }
}
</script>
The <slot> tag is used to specify where the nested content should be displayed.
EDIT:
So you can actually pass components as props, and display them using the <component :is="..." /> method.
The reason that it's not working is that your original HugeBox.vue component doesn't have access to the Dog component as a template variable. You have to assign it to a data property first:
<template>
<Box :myComponent="dog"/>
</template>
<script>
import Dog from "../test/Dog.vue";
import Box from "../test/Box.vue";
export default {
components: { Box },
data() {
return {
dog: Dog // assign the Dog component object to data, allowing it to be passed as a prop
};
}
};
</script>
You have to import your Dog component inside your Box component.
Not inside your HugeBox component.
HugeBox.vue
<template>
<Box :myComponent = "'Dog'"/>
</template>
<script>
import Dog from '../test/Box.vue';
export default {
components: {Box}
}
</script>
Box.vue
<template>
<div>
<p>Box with</p>
<component :is = "myComponent"/>
</div>
</template>
<script>
import Box from '../test/Dog.vue';
export default {
props: {
myComponent: {
type: [String, Object],
}
},
components: { Dog }
}
</script>
Dog.vue
<template>
<p>Dog</p>
</template>

Dynamically loading child components in parent component

I am a beginner in Vue.js so please bear with me. I created two child components and one parent component. I am able to load both the components in the parent components. Now, one of my child component should load in the parent component by default. It will have a link, and when clicked, it should load the other child component within the same parent component, by passing and ID value from itself to the loading component. Below is my code.
Parent component (App.vue):
<template>
<div id="app">
<body>
<main>
<div class="main-body">
<component v-bind:is="currentComp">
</component>
</div>
</main>
</body>
</div>
</template>
<script>
import HomeContent from './components/HomeContent.vue';
import DetailContent from './components/DetailContent.vue';
import { changeRoute } from './main.js';
export default {
name: 'app',
data() {
return {
currentComp: 'HomeContent'
};
},
components: {
HomeContent, //this is the default child component
DetailContent, //this is the other child component which should get loaded dynamically
},
created() {
changeRoute.$on('switchComp', comp => {
this.currentComp = comp;
})
}
}
</script>
Default child component (HomeContent.vue):
<template>
<div>
This is Default child component
Click to load the other child dynamically
</div>
</template>
<script>
import { changeRoute } from '../main.js';
export default {
name: 'HomeContent',
props: {
msg: String
},
methods: {
switchComponent(comp) {
changeRoute.$emit('switchComp', comp);
}
}
}
</script>
The other child component (DetailContent):
<template>
<div>
This is the other loaded child component.
</div>
</template>
<script>
export default {
name: 'DetailContent',
props: {
msg: String
},
mounted(){
alert('mount ok');
}
}
</script>
With the above approach DetailContent get loaded on click of a link from the HomeContent. Now, how can pass some data (e.g. ID = 1) from HomeContent to DetailContent. Any help with this please?

VueJS pass all props to child component

I am quite new to VueJS. In react you can easily use rest params for passing props to children. Is there a similar pattern in Vue?
Consider this parent component that has a few children components:
<template>
<header class="primary-nav">
<search-bar :config="searchBar"><search-bar>
// ^^^^ this becomes the key on the props for SearchBar
header>
</template>
export default {
data(){
return {
... a few components ...
searchBar : {
model: '',
name: 'search-bar',
placeholder: 'Search Away',
required:true,
another: true
andanother: true,
andonemoreanotherone:true,
thiscouldbehundredsofprops:true
}
}
}
}
<template>
<div :class="name">
<form action="/s" method="post">
<input type="checkbox">
<label :for="config.name" class="icon-search">{{config.label}}</label>
<text-input :config="computedTextInputProps"></text-input>
//^^^^ and so on. I don't like this config-dot property structure.
</form>
</div>
</template>
export default {
props: {
name: String,
required: Boolean,
placeholder: String,
model: String,
},
computed: {
computedtextInputProps(){
return justThePropsNeededForTheTextInput
}
}
...
What I don't like is that the props are named-spaced with the key config, or any other arbitrary key. The text-input component ( not shown ) is a glorified input field that can take a number of attributes. I could flatten the props when the component is created, but is that generally a good idea?
I am surprised this question hasn't been asked before.
Thanks.
Edit: 10-06-2017
Related: https://github.com/vuejs/vue/issues/4962
Parent component
<template>
<div id="app">
<child-component v-bind="propsToPass"></child-component>
</div>
</template>
<script>
import ChildComponent from './components/child-component/child-component'
export default {
name: 'app',
components: {
ChildComponent
},
data () {
return {
propsToPass: {
name: 'John',
last_name: 'Doe',
age: '29',
}
}
}
}
</script>
Child Component
<template>
<div>
<p>I am {{name}} {{last_name}} and i am {{age}} old</p>
<another-child v-bind="$props"></another-child> <!-- another child here and we pass all props -->
</div>
</template>
<script>
import AnotherChild from "../another-child/another-child";
export default {
components: {AnotherChild},
props: ['name', 'last_name', 'age'],
}
</script>
Another Child component inside the above component
<template>
<h1>I am saying it again: I am {{name}} {{last_name}} and i am {{age}} old</h1>
</template>
<script>
export default {
props: ['name', 'last_name', 'age']
}
</script>
Parent component
You can pass as many props as you want to child component
Child Component
Once you are satisfied with all the props, then you can use v-bind="$props" inside your child component to retrieve all the props.
Final Result:
Done:)
In Vue 2.6+, in addition to attributes passing,
events/listeners can be also passed to child components.
Parent component
<template>
<div>
<Button #click="onClick">Click here</Button>
</div>
</template>
<script>
import Button from "./Button.vue";
export default {
methods:{
onClick(evt) {
// handle click event here
}
}
}
</script>
Child component
Button.vue
<template>
<button v-bind="$attrs" v-on="$listeners">
<slot />
</button>
</template>
Update [2021-08-14]:
In Vue 3, $listeners will be removed. $attrs will have both listeners and attributes passing to the child component.
Child component
Button.vue
<template>
<button v-bind="$attrs">
<slot />
</button>
</template>
Migration Guide
In the child component, you can bind $attrs and $props.
<template>
<button v-bind="[$props,$attrs]" v-on="$listeners">
<slot />
</button>
</template>
v-bind="[$attrs, $props]"
This one works for me. $attrs doesn't include props.

Categories

Resources