Vuejs 3 : components scope, and how to provide components to slots - vue.js

I'd like in Vue 3 to be able to provide/overwrite existing components to slots by doing something like:
<ProvideFun>
<p>
Hello
<FunA></FunA>
</p>
</ProvideFun>
<ProvideOtherFun>
<p>
Hello
<FunA></FunA>
</p>
</ProvideOtherFun>
in order to be allowed to use some components (here <FunA>) inside others components (here <ProvideFun> and <ProvideOtherFun>). Note that ProvideFun and ProvideOtherFun may provide different versions of the <FunA> component.
How could I do something like that?
You can find a demo here.

Option 1: Provide/inject components
The parent (i.e., ProvideFun or ProvideOtherFun) could provide its own component definition (named "comp") to be used in FunA:
<!-- ProvideFun.vue -->
<script setup>
import { provide } from 'vue'
import CompA from './CompA.vue'
👇
provide('comp', CompA)
</script>
<template>
<h1>
Provide fun
</h1>
<slot />
</template>
<!-- ProvideOtherFun.vue -->
<script setup>
import { provide } from 'vue'
import CompB from './CompB.vue'
👇
provide('comp', CompB)
</script>
<template>
<h1>
Provide other fun
</h1>
<slot />
</template>
Make FunA.vue a <component> wrapper that injects the "comp" component definition from a parent:
<!-- FunA.vue -->
<script setup>
import { inject } from 'vue'
👇
const comp = inject('comp')
</script>
<template> 👇
<component :is="comp" />
</template>
This requires registering the FunA placeholder component before it could be used in ProvideFun/ProvideOtherFun:
<script setup>
import ProvideFun from './ProvideFun.vue'
import ProvideOtherFun from './ProvideOtherFun.vue'
👇
import FunA from './FunA.vue'
</script>
<template>
<ProvideFun>
<p>
Hello
<FunA />
</p>
</ProvideFun>
<ProvideOtherFun>
<p>
Hello
<FunA />
</p>
</ProvideOtherFun>
</template>
demo 1
Option 2: Components via slot props
Bind the CompA component definition as a slot prop (named "FunA") in ProvideFun.vue, and CompB in ProvideOtherFun.vue:
<!-- ProvideFun.vue -->
<script setup>
import CompA from './CompA.vue'
</script>
<template> 👇
<slot :FunA="CompA">
</template>
<!-- ProvideOtherFun.vue -->
<script setup>
import CompB from './CompB.vue'
</script>
<template> 👇
<slot :FunA="CompB">
</template>
Then use a <component> to render it in the parent:
<script setup>
import ProvideFun from './ProvideFun.vue'
import ProvideOtherFun from './ProvideOtherFun.vue'
</script>
<template>
<ProvideFun> 👇
<template v-slot="{ FunA }">
<p>
Hello 👇
<component :is="FunA" />
</p>
</template>
</ProvideFun>
<ProvideOtherFun> 👇
<template v-slot="{ FunA }">
<p>
Hello 👇
<component :is="FunA" />
</p>
</template>
</ProvideOtherFun>
</template>
demo 2

Related

How can I wrap every v-if in my Vue code in a transition?

The task of having to write
<transition name="fade">
<div v-if="condition">
</div>
</transition>
Is manual labour. Is there any shortcut way of wrapping every v-if with a transition?
You can create a custom Vue component:
// AppTransition.vue
<template>
<transition name='fade'>
<div v-if='show'>
<slot />
</div>
</transition>
</template>
<script>
export default {
props: {
show: Boolean
}
}
</script>
and then use it as follows:
<template>
<AppTransition :show='condition'>
<div>Hello, world</div>
</AppTransition>
</template>
<script>
// You can avoid importing it everywhere by making it a global component, see https://vuejs.org/guide/components/registration.html
import AppTransition from './AppTransition'
export default {
components: { AppTransition }
}
</script>

Display a specific route in a different router-view

I've got a Vue 3 app with Vue Router 4 and I want to achieve this:
<template>
<v-app>
<router-view></router-view> //This is where login would be
<app-layout>
<router-view /> //Everything else
</app-layout>
</v-app>
</template>
<script lang="ts" setup>
import AppLayout from "./components/AppLayout.vue";
</script>
I've got an <app-layout> component which includes navbar and a sidebar, but I don't want to display those when the user is not logged in. I also don't want to wrap each component in <app-layout> individually.
Is there a way I can achieve this?
You can use the Vue-router's v-slot API like this :
<template>
<v-app>
<router-view v-slot="{ Component, route }">
<component v-if="route.name === 'Login'" :is="Component" />
<app-layout v-else>
<component :is="Component" />
</app-layout>
</router-view>
<!-- Or -->
<router-view v-slot="{ Component, route }">
<component :is="route.name === 'Login' ? 'template' : 'app-layout'">
<component :is="Component" />
</component>
</router-view>
</v-app>
</template>
<script lang="ts" setup>
import AppLayout from "./components/AppLayout.vue";
</script>
And in your router, for the login route, don't forget to add the route name :
{
name: 'Login', // important
// ...
}

Passing a HTML tag in vue js

I have a component which has a <p> tag inside, but would like it to be a <h1> tag sometimes, how to pass the prop ?
<template>
<p>Hello world</p>
</template>
Pass it as prop then use component to render it :
<template>
<component :is="tag">Hello world</component >
</template>
<script>
export default{
name:'MyComponent',
props:['tag']
}
</script
then use the component like <MyComponent tag="h1" />
You could make MyComponent more dynamic accepting any content by using slots :
<template>
<component :is="tag">{{msg}}</component >
</template>
<script>
export default{
name:'MyComponent',
props:['tag','msg']
}
</script
then use it like <MyComponent tag="h1" >hello world</MyComponent>
Would recommend using slot for that component, which will be something like this
<template>
<slot name="content"></slot>
</template>
When you use your component you can just do this
<Component>
<template #content>
Your content here, whatever you like
<template/>
<Component/>

any way to not use suspend in vue 3 for composition api async?

I have a composition API to fetch data by Async in setup() but I should define like this in another component
<Suspense>
<template #default>
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
if I don't use <Suspense>, that component not rendering in my app, how can i solve this?
You can achieve this by using v-if and v-else. You should also make the data reactive in case you haven't already done that.
<template>
<div v-if="data">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
<div v-else>
Loading...
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
setup() {
const data = reactive({});
return {
data,
};
},
});
</script>

VueJS extends component with slots

I have a BaseModal Component like this :
<template>
<div class="base-modal" :class="{open: isOpen}">
<div class="modal">
<h2 class="modal-title">
<slot name="title"></slot>
</h2>
<div class="modal-content">
<slot name="content"></slot>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
#Component({})
export default class BaseModal extends Vue {
public isOpen: boolean = false;
// Some methods
}
</script>
I want to create an other modal component that will extend this one and pass it the content for the named slots. Here is where I am :
<template>
// How to give slots content here
</template>
<script lang="ts">
import BaseModal from "#app/components/BaseModal.vue";
import { Vue, Prop, Component } from "vue-property-decorator";
#Component({
extends: BaseModal,
})
export default class OtherDropdown extends Vue {
}
</script>
The extends is working but I can't figure out how to pass content to the named slot of the extended component. Not to have to write again all the BaseModal template.
Thanks
I'm using VueJS 2.6.8
You would want to use components named slots like this:
<template>
<BaseModal>
<template v-slot:title>
Some Title
</template>
<template v-slot:content>
Some content goes here
</template>
</BaseModal>
</template>
You can read more about named slots here: https://v2.vuejs.org/v2/guide/components-slots.html#Named-Slots
Hope this helps!