Vue 3 removed the $children property and butchered $slots. However I can't find another solution for this scenario I'm having:
I have a component Checkbox. This component renders a checkbox. It also has a default slot. Depending on whether there was anything in that default slot I need to show/hide an element and apply some classes to the Checkbox' root element for styling and animation purposes of elements around it. I don't need access to the actual children, only the information whether there are any.
In Vue 2 I could write something like this:
if (this.$slots.default) {
// do something
}
The same thing in Vue 3 always is true, because it's a function now. However, calling that function returns nonsense:
console.log(this.$slots.default())
// Returns:
// [ Vnode ]
It doesn't matter whether I put something into the default slot or not, the end result is always the same, only with some elements buried in the Vnode tree having changed.
How am I supposed to find out whether there are any children in Vue 3?
For reference, the component template I used before:
<transition name="slide-down">
<div class="children" v-if="$slots.default">
<slot></slot>
</div>
</transition>
The above and below does not work in Vue 3 (the div always is being rendered):
<transition name="slide-down">
<div class="children" v-if="$slots.default()">
<slot></slot>
</div>
</transition>
Imagine that you have a user list component UserList that shows users in scrollable view. Each users details are represented with UserDetails component wrapped with Card component.
UserList
<template>
<Card>
<UserDetails />
</Card>
<Card>
<UserDetails />
</Card>
...
</template>
Now imagine that you're using this UserList component and want to re-implement the wrapper for UserDetails without modifying it's contents.
Adding a slot would work for replacing the wrapper but everything inside the wrapper would need to be re-implemented as well.
It would be nice if we could write Vue like this:
UserList
<template>
<slot name="wrapper">
<Card>
<template #content>
<UserDetails />
</template>
</Card>
</slot>
...
</template>
Consuming component:
<UserList>
<template #wrapper>
...
<NewImplementation>
<slot :name="content" />
</NewImplementation>
</template>
</UserList>
It would work by using slots in "reverse" way of how we're used to.
This isn't valid syntax but I bet someone else has thought about the same problem. The need to replace some content with slot but not all of it.
Wrapper component could be given as property but I think it's not the correct solution because we have slots to avoid doing just that.
Are there any good solutions?
If I understood right, what you're looking for is Teleports (previously called Portals) which is only available in version 3.0
What you want is problematic. Vue has no direct support for something like that.
Let me establish some naming conventions before I try to explain why:
Child - UserList component
Parent - component consuming Child
Main problem in this scenario is that slot content is completely generated in the consuming component and just passed to a Child component using
$slots - this is for normal slots. $slots.wrapper is just a static array of VNodes
$scopedSlots - for scoped slots. $scopedSlots.wrapper is a function. When Child renders it calls that function and renders returned an array of VNodes
(Note: In Vue 3 it is just $slots. All slots are functions)
What that means is that Child component has no way to somehow change the content of the slot (e.g. include something in the middle). It can pass some data into it (slot scope) but that's all...
Possible workarounds:
1. Just pass a wrapper component as a prop
Wrapper component could be given as property but I think it's not the correct solution because we have slots to avoid doing just that.
Well not really. Slots have a different purpose. If you want to change the wrapper and let the rest of the Child intact, this is pretty good and simple solution
2. Give a Parent the component it should render inside the wrapper using slot scope
This is something very well demonstrated in route-view component of Vue Router 4.x - see the docs
<UserList v-slot="{ UserComponent, UserData }">
<NewWrapper>
<component :is="UserComponent" v-bind:data="UserData" />
</NewWrapper>
</UserList>
So you have <component :is="UserComponent" /> instead of <UserDetails />. This is useful as the UserList component can now control what component is rendered inside the wrapper (and even change it dynamically)
The objective is to have a base html element in the Vue component:
<template>
<div>
<template>
.....
export default {
name: 'derivedDiv'
props: [definedProp]
}
when specifying I want to specify this as
<derived-div defined-prop="20" class="undef-prop-class" id="id-to-be-passed-to-div" key="not-def-but-to-div>"
then capture these props and put them to the div
this something like **kwargs in python when deriving classes.
created (){
foreach (undefined property.... assign property to the base div)
This way I want to be able to inherit basic html properties for a vue component and have flexibility to use them later rather than defining them first.
Example:
<parent-component :prop1="hello" :prop2="hello2">
</parent-component>
parent-component.vue:
<template>
<div>
<child1-component :obj1="prop1"></child1-component>
<child2-component :obj2="prop2"></child2-component>
</div>
</template>
<script>
export default {
//code
};
</script>
*here I want to just pass these props (prop1, prop2 ) directly, without the need to write them inside the parent-component like this:
props: ["prop1", "prop2"],
There is no way you can pass to all those children component without using either props or any kind of state management.
You can still simply manage all those state using the Global Event Buss. Global Event Bus is simple enough for your application if you don't want to use Vuex. In this case your props1, props2 will be store globally and can access by your child of child components easily without using props.
I have a parent component with the following line
<router-view :product-id="productId" :data-source="attributes"></router-view>
depending on the context it renders one of the two components defined in the router config
path: 'parent',
component: Parent,
children:
[
{
path: 'edit',
component: Edit,
children:
[
{
path: 'attribute/:id',
component: Attribute,
}
]
},
{
path: 'grid',
component: Grid,
}
]
The thing is that the product-id and data-source props are available only in the Edit and Grid components. I'd like to have them available in the Attribute component as well as the Edit component is just a background with some static text (common for many components).
As a workaround I've created a propertyBag prop in the Edit component that passes an object down. That's the way I use it in the parent component
<router-view :property-bag="{ productId:productId, dataSource:dataSource, ...
and the Edit component
<router-view :property-bag="propertyBag"></router-view>
Is there a simpler way to achieve it ?
Vue $attrs is the new way to propagate props
From the Docs:
vm.$attrs
Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props. When a component doesn’t have any declared props, this essentially contains all parent-scope bindings (except for class and style), and can be passed down to an inner component via v-bind="$attrs" - useful when creating higher-order components.
For more information, see Vue.js API Reference - $attrs
Have you looked at vuex. It's really quite easy to use and allows you to store data for your entire app in a single data store. This means you don't have to keep passing data through props, you can just access variables set in the store.
Here is a link to vuex docs (What is Vuex)
https://vuex.vuejs.org
You have to declare the props and bind them to pass them to the child.
Have a look at https://v2.vuejs.org/v2/api/#v-bind for available options
specifically, this may be of interest
<!-- binding an object of attributes -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
<!-- but you can also... -->
<div v-bind="allProps"></div>
This means you can pass down the object, and have the child parse the appropriate props. This means that the child has to have the props defined in order to catch them. So what you may be able to do is, in the case of the parent, have :propBag="propBag" and inside edit, pass down v-bind="propBag", and that will use the correct props at the child level
for vue > 2.0
v-bind="$attrs" it's sufficient, or you can declare them at data(), with [this.$attrs]
Solution possible from Vue 2.2.0
provide / inject
This pair of options are used together to allow an ancestor component to serve as a dependency injector for all its descendants, regardless of how deep the component hierarchy is, as long as they are in the same parent chain.
https://fr.vuejs.org/v2/api/index.html#provide-inject
How to pass multiple Props Downstream to Components
Use case: Props that you only need on the n-th child components, you can simply forward downstream without having to define the same props all over again in each component.
Correction: v-bind="$attrs" works just fine. Just make sure the parent also uses v-bind="$attrs" and not v-bind="$attr" ('s' was missing) which was the error that made me think v-bind="{ ...$attrs }" was needed.
However, I think you should still be able to use v-bind="{ ...$attrs }" to access all previous attributes, even if parents didn't explicitly propagated them.
How to:
Based on Alexander Kim's comment, it must be v-bind="{ ...$attrs }".
... is needed to pass the attributes of the previous component (parent) as $attrs only passes the attributes of the current component.
v-bind="{ ...$attrs }"
You must pass all data via props to children components. You don't have to pass it as an object but Vue.js does require all data to be passed to children. From their documentation:
Every component instance has its own isolated scope. This means you cannot (and should not) directly reference parent data in a child component’s template. Data can be passed down to child components using props.
So you are doing it in the correct manner. You don't have to create an object, you are able to pass as many props as you would like but you do have to pass the from each parent to each child even if the parent is a child of the original "parent".