Vue <teleport> in Nuxt - vue.js

While designing client-rendering SPA, <teleport to="body"> of Vue3 works well.
I can teleport dialog component to <body>.
<--! dialog component example-->
<template>
<teleport to="body">
<div class="dialog">
<slot></slot>
</div>
</teleport>
</template>
However, it's failed when I try to use the same way in Nuxt static mode.
Does Nuxt support "teleport" method?
Is there any other workaround dealing with teleport in Nuxt static application?

Portals/Teleport arrived with Vue 3. This is not yet supported in Nuxt, as it is still running on v2. If necessary, you can likely find alternative third party packages for this in the meantime.

I may misunderstand what you're looking for but one solution is using <ClientOnly>. Most of the time we only need to render Modal in client-side (without SSR) anyway.
<template>
<div class="modal_container">
<ClientOnly>
<Teleport to="body">
<div class="modal">
Hello World
</div>
</Teleport>
</ClientOnly>
</div>
</template>

Related

Register multiple Vue components in parent component

I have a global sidebar component TheSidebar.vue:
<template>
<aside>
<slot></slot>
</aside>
</template>
In Blogs.vue (a page component) I try to register two components.
<template>
<div>
<h1>Experiences</h1>
<TheSidebar>
<SearchBlog />
<CategoryCheckboxFilter />
</TheSidebar>
<ExperienceList />
</div>
</template>
It seems like I cannot register two components in a slot?
Is this a good setup anyway and who do I have to achieve this?
Update
It's just working fine now and I can register more than one component in a <slot />. I think some webpack building issue before.

What does it actually means - Use created + activated for keep-alive components in Vuejs

Initially, I am fetching data from api in the created hook which is perfectly working.
created() {
this.fetchInformation()
}
But I was having look over best practices for lifecycle hooks and I came to this line You need to fetch some data for your component on initialization. Use created (or created + activated for keep-alive components)
I also tried to look for relevant articles or information on the internet.
Url for reference - https://alligator.io/vuejs/component-lifecycle/
My component is rendering inside keep-alive so I tried this for the test purpose.
activated() {
this.fetchInformation()
}
Instead of created, now as expected everytime the component activates it execute the api call which is really cool. But I still want to understand what this actually created + activated as I am using activated or created but if I am correct just by reading that I should do them both.
Please let me know if anything else required to understand my question.
Thanks
Use correctly keep-alive!!
Incorrect:
<template>
<div>
<div v-if="canRender">
<keep-alive>
<my-component />
</keep-alive>
</div>
</div>
<template>
Incorrect:
<template>
<div>
<keep-alive>
<div v-if="canRender">
<my-component />
</div>
</keep-alive>
</div>
<template>
Correct:
<template>
<div>
<div>
<keep-alive>
<my-component v-if="canRender" />
</keep-alive>
</div>
</div>
<template>

vue.js Mount component to app root

I have a modal.vue component as follows:
<template>
<transition name="modal-transition">
<div class="modal-body" v-if="displayed">
<div class="modal-overlay" #click="displayed = false"></div>
<div class="modal-content">
<slot/>
</div>
</div>
</transition>
</template>
How do I mount this component to the applications root element rather than in place?
For crude inaccurate example:
<body>
<div id="app">
<div class="header"></div>
<div class="nav"></div>
<div class="stage">
<div class="sub-nav"></div>
<div class="content">
<modal :display.sync="display">MY MODAL</modal> <-- Don't mount here...
</div>
</div>
<-- Mount here instead...
</div>
</body>
The current issue is that my sites header and navigation is layered on top of my modal and it's darkened full screen overlay instead of layered behind the modal overlay.
Update for Vue 3
There is now a built in feature called teleport which allows mounting parts of your component template to any DOM element.
The example from the OP would look like something like this
<!-- MyModal.vue -->
<template>
<transition name="modal-transition">
<div class="modal-body" v-if="displayed">
<div class="modal-overlay" #click="displayed = false"></div>
<div class="modal-content">
<slot/>
</div>
</div>
</transition>
</template>
<!-- SomeDeeplyNestedComponent.vue -->
<template>
<teleport to="#app">
<!-- Can still receive props from parent -->
<MyModal :my-prop="foo">
<!-- slot content -->
</MyModal>
</teleport>
</template>
Vue 2
Move the elements own self to the element of applications root may be achieved in two ways, Using a portal as a preferred solution or using an append.
Using a Portal (Preferred Method)
PortalVue is a set of two components that allow you to render a
component's template (or a part of it) anywhere in the document - even
outside the part controlled by your Vue App!
https://portal-vue.linusb.org/
Using an Append (Not best practice)
If adding a portal library is too heavy, using an append is allowed but lightly discouraged officially in the VUE docs.
Typically this particular mount position will satisfy a z-index overlay for your own modal or dialog popup that you require to render over the top of the entire app. You can always substitute this.$root.$el in this example for a different element target using standard getElementBy or querySelector functions.
Here the element is being moved not destroyed and re-added, all reactive functionality will remain in tact.
<script>
export default {
name: 'modal',
...
mounted: function() {
this.$root.$el.append(this.$el);
},
destroyed: function() {
this.$el.parentNode.removeChild(this.$el);
}
}
</script>
On mounted the element is moved inside of where the top level VUE app instance is mounted.
On destroyed removes the placeholder DOM comment for the migrated component from the new parent to prevent orphaned duplication each time the component remounts it's self.
VUE officially states not to destroy an element outside of VUE so this is not to be confused with that statement, here the component has already been destroyed.
This DOM comment duplication will typically happen when for example switching views with vue-router as this mechanism mounts and dismounts all components in a router view each time vue-router view state changes.
This behaviour is a bug cause by vue-router, the object is destroyed properly by VUE render manager but an index reference remains by mistake, using a portal package resolves this issue.
Here is the result:

How do I use conditional rendering on template tag?

According to the Vue documentation I should be able to add the v-if condition to the <template> tag:
<template v-if="false">
<div>Invisible text</div>
</template>
But this will not hide the element, however it does work when added to the child element:
<template>
<div v-if="false">Invisible text</div>
</template>
Any suggestions?
I'm including the template in another .vue file:
<template>
<div id="app">
<H1 class= "main-title">Title</H1>
<span class="components">
<testtemplate></testtemplate>
</span>
</div>
</template>
The template tag of a single-file component is not rendered by Vue like normal <template> tags. It is simply one of the placeholders, along with <script> and <style> that vue-loader uses to build the component. The root element of that template is what will be the root in the component.
But, even if it worked the way you want, there would be no difference between your first and second example. Using v-if on the root will prevent the entire component's template from rendering if set to false.
Had this problem with VUE3. Using SFC just nest tag template inside another tag template :
<template>
<template v-if="false">
You won't see this
</template>
</template>

Vue named slots do not work when wrapped

I have a responsive-menu component which I want to use named slots inside of this up my template markup:
<template>
<div class="responsive-menu">
<div class="menu-header">
<slot name="header"></slot>
</div>
</div>
</template>
Whenever I try my named slot like this it work perfectly fine:
<responsive-menu>
<h3 slot="header">Responsive menu header</h3>
</responsive-menu>
However as soon as I wrap it with a class nothing shows up anymore.
<responsive-menu>
<div class="container">
<h3 slot="header">Responsive menu header</h3>
</div>
</responsive-menu>
What is going on here? Shouldn't I just be able to wrap the named component? Which does it appear that my named slots need to be direct children of my Vue component?
It does not work because your "wrapped slot" isn't direct child of responsive-menu tag.
try something like that:
<responsive-menu>
<div class="container" slot="header">
<h3>Responsive menu header</h3>
</div>
</responsive-menu>
jsfiddle
It works with Vue >= 2.6. Just upgrade vue and vue-template-compiler.