can you pass router-view as a prop in vuejs - vue.js

Is it possible to pass as a prop when calling another component?
Essentially, I have components, and views, I build my views using various components. I want to have 1 styled component which I can reuse, so I was thinking to have a WebsiteLayout.vue:
<template>
<a-layout-content :style="{ padding: '0 24px', minHeight: '280px' }" />
{{ content }}
</template>
<script>
export default {
name: "View",
components: {
},
props: ["content"],
};
</script>
And in my App.vue:
<template>
<Content content=<router-view /> />
</template>
This isnt correct, but wondering if something like this is possible, and how I could achieve it?

With Vue 3 and Router 4, you can do something like
<router-view v-slot="{ Component }">
<SomeComponent>
<component :is="Component" />
</SomeComponent>
</router-view>
and then
//SomeComponent.js
<template>
<div class="wrapper">
<slot></slot>
</div>
</template>
here is an example with transition component as a wrapper for the router-view component
to know more about scoped slots you can see this

Related

vuejs transitions only on some views

I have transitions working between the pages of my vuejs application, defined in App.vue like so:
<template>
<div class="container mb-auto">
<router-view v-slot="{Component}" >
<transition name="slide" mode="out-in">
<component :is="Component" :key="route.path"></component>
</transition>
</router-view>
</div>
<TheFooter v-if="withMenu" />
</template>
// and definition of transitions in css
I don't want this to work between all views (pages) of my app, but only between views who's url starts with /welcome
How do I use some transitions between some pages, and other transitions between other pages?
You can use the JS transitions hooks as shown here: https://vuejs.org/guide/built-ins/transition.html#javascript-hooks
And make a check if you're on the correct path or not, example on a /welcome-home path below
<template>
<div>
<transition #before-enter="onBeforeEnter">
<!-- ... -->
</transition>
</div>
</template>
<script>
export default {
methods: {
onBeforeEnter() {
if (this.$route.path.startsWith('/welcome')) {
// cool transitions!
}
},
},
}
</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
// ...
}

My dynamic component (layout) doesn't work with named slots in vuejs

I have problems to combine dynamic generated layouts with named slots.
To define my layouts I'm using "component :is"
//app.vue
<template>
<component :is="layout">
<router-view />
</component>
</template>
<script>
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
},
</script>
//layouts/default.vue
<template>
<div>
<div>
<slot name="header" />
</div>
<div>
<div>
<slot name="sidebar" />
</div>
<div>
<slot name="default"/>
</div>
</div>
</div>
</template>
// views/page.vue
<template>
<div>
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</div>
</template>
But now I get this error inside my code
'v-slot' directive must be owned by a custom element, but 'div' is not.
and console displays this error
<\template v-slot> can only appear at the root level inside the receiving component
If I remove the main div I get the error
The template root requires exactly one element.
What I'm doing wrong?
This is not easy to explain so please cope with me...
I really understand what you are trying to do but unfortunately it is not possible in Vue.
Reason for that is slots are more template compiler feature than runtime feature of Vue. What I mean by that ? When Vue template compiler sees something like <template #header>, it will take the inner content and compile it into a function returning virtual DOM elements. This function must be passed to some component which can call it and include the result in it's own virtual DOM it is generating. To do that template compiler needs to know to what component it should pass the function (that is the real meaning of 'v-slot' directive must be owned by a custom element, but 'div' is not. error message...ie compiler is "looking" for a component to pass the slot content to...)
But you are trying to use the slots as if they were "discoverable" at runtime. For your code to work the dynamic layout component must at runtime somehow discover that it's child (also dynamic thanks to <router-view />) has some slot content it can use. And this is not how slots work in Vue. You can pass the slot content your component receives from parent to a child components but do not expect that parent component (layout in this case) can "discover" slot content defined in it's child components...
Unfortunately only solution for your problem is to import the layout component in every "page" and use it as a root element in the template. You can use mixins to reduce code duplication (to define layout computed)
#/mixins/withLayout.js
export default = {
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
}
}
views/page.vue
<template>
<component :is="layout">
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</component>
</template>
<script>
import withLayout from '#/mixins/withLayout'
export default {
mixins: [withLayout]
}
</script>

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/>

Passing value from parent to child using slot probably

I probably don't understand how it should be done. I spent a few hours to achieve this functionality but with no luck. Here is what I have:
Child
<template>
<div>
Data from dialog: {{aaa}}
</div>
</template>
<script>
export default {
name: 'frm',
props: [
'aaa'
]
}
</script>
Parent:
<template>
<div>
<slot :aaa="some"></slot>
</div>
</template>
<script>
export default {
name: 'dlg',
data: () => ({
some: 'data from dialog'
})
}
</script>
View:
<template>
<div>
<dlg>
<frm></frm>
</dlg>
</div>
</template>
<script>
import Dialog from '#/components/dialog.vue'
import Frm from '#/components/frm.vue'
export default {
name: "View",
components: {
'dlg': Dialog,
'frm': Frm
}
};
</script>
Edit: Real code
dialog-template:
<template>
<v-dialog
v-model="internal.dialogOpened"
>
<!-- ... -->
<slot :aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form /> <!-- just regular component in which I want to get value passed through slot in dialog-template -->
</dlg-template>
</template>
<script>
import DlgTemplate from '#/components/site/dialogs/dialog-template.vue'
export default {
// ...
components: {
'dlg-template': DlgTemplate,
'task-details-form': DetailsForm,
},
I want to avoid passing prop in View but I don't know how :/ I've read about 'slot-scope' unfortunately with no success. How to achieve such functionality?
Edit: real code
Based on your real world code, you were only missing the attachment of the scope, see below.
dialog-template:
<template>
<v-dialog v-model="internal.dialogOpened">
<!-- ... -->
<slot :aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form v-slot="{ aaa }">
<!-- you can use the var `aaa` here -->
</task-details-form>
</dlg-template>
</template>
I'd still wager if you want to use aaa inside task-details-form component you have to pass it down as a prop. But it looks wierd to me, because I'm unsure of the execution order right now (v-slot vs v-bind), but try it like this:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<task-details-form v-slot="{ aaa }" :your-a-prop-name="aaa" />
</dlg-template>
</template>
Edit 2: after testing
v-bind shorthand is not working on <slot>:
dialog-template:
<template>
<v-dialog v-model="internal.dialogOpened">
<!-- ... -->
<slot v-bind:aaa="'dsada'"></slot>
</v-dialog>
</template>
details-task-dialog:
<template>
<dlg-template large position='right' :onclose="close" :load="loadRetry">
<template v-slot="{ aaa }"> <!-- has to preceed v-bind -->
<task-details-form :propertyOnComponent="aaa" /> <!-- now you cand bind it -->
</template>
</dlg-template>
</template>
Original:
I think you misunderstood slots. Check the docs:
That slot has access to the same instance properties (i.e. the same “scope”) as the rest of the template. The slot does not have access to child’s scope.
Slots
So you could do that, but then your code becomes:
<template>
<div>
<dlg>
<frm>
<child :aaa=“dialogData” />
</frm>
</dlg>
</div>
</template>
Frm:
<template>
<div>
From component
<slot></slot>
</div>
</template>
If you would define a slot on frm as you are suggesting, you would be able to fill that slot with a specific template and you can receive(!) the scope for that slot.
The deprecated slot-scope which you mentioned would provide you with a bound context from the child to be exposed in your overriding slot in the parent, that’s the opposite of what you want.
Just out of curiosity, why not send down the dialog data to the form as a property? That’s exactly what it’s for.