Using nuxt-child's layout instead of the parent's - vue.js

I am using Nested Routes and <nuxt-child> with layouts on nuxt.js.
The parent is holding the <nuxt-child> component, which is viewing the parent's subroutes.
I want Nuxt to use the parent's layout whenever I'm on its route / subroutes, though if any child has its own layout, I want Nuxt to use it instead of the parent's for it and its subroutes.
My file arrangement is as such:
pages/
--- parent/
------ child.vue
--- parent.vue
layouts/
--- parentLayout.vue
--- childLayout.vue
pages/parent.vue
<template>
<nuxt-child />
</template>
<script>
export default {
layout: "parentLayout",
};
</script>
pages/parent/child.vue
<template>
<span>child.vue</span>
</template>
<script>
export default {
layout: "childLayout",
};
</script>
So based on this example:
I want Nuxt to use the layout 'parentLayout' whenever I'm on the parent/ route or subroutes, though I want to show a different layout - 'childLayout' whenever I'm on parent/child or its subroutes.
The issue is, even if I go to parent/child, the layout that is being used is 'parentLayout' and not 'childLayout', which is because I am using <nuxt-child>.
I have tried using this.$nuxt.setLayout(), but Nuxt still seems to load the parent's layout first, which makes the rendering look off.
How do I overcome this issue and use the wanted layout for child.vue and it's subroutes?

A solution for this is to conditionally set layout in the parent.
export default {
name: 'Parent',
layout ({ route }) {
if (route.name === 'parent-child-id') {
return 'childLayout'
}
return 'parentLayout'
},
data () {
return {}
}
}

Related

Vue Test Utils: how to pass Vuelidate validation rules to child components?

while trying to write a component test by using vue test utils, testing interaction between child components and stuff, I am stuck due to usage of Vuelidate from child components. Below is an example simplified:
// parent component code
<template>
<div>
<childA />
</div>
</template>
//childA code
<template>
<input v-model="value" />
</template>
<script>
...
validations: {
value: {
required
}
}
...
</script>
// parent component test
...
const wrapper = mount(MyParentComponent, {
...,
components: {
childA,
},
validations: {
value: required
},
...
})
I have tried to find a solution out there that I could mount (note here that I WANT to mount also the child components, so shallow-mount is not what I look for) the child component, with it's respective Vuelidate validation rules, but I still haven't found any solution.
Instead, my test gives me errors like:
Cannot read property `value` of undefined
which makes sense, since the test cannot access the child component's $v instance.
Has anyone achieved it so far?
For answering your question and after i've did some test i believe you missed the data part inside your mount
mount: render child components
shallowMount: doesn't render child components
MyParentComponent need to have in the options the structure of you're child component so this is why he is returning the error
And i saw that you're passing the import of your component directly but don't forget that your test folder is outside of your src folder
import ChildA from "#/components/ChildA";
will not work instead i propose to use absolute path directly to import your child component or use a configuration to resolve them
const wrapper = mount(MyParentComponent, {
data() {
return {
value: null
}
},
components: {
ChildA: () => import('../../src/components/ChildA'),
},
validations: {
value: required
},
})

Paginated async Component doesn't trigger setup() on route change

I have a paginated component. The async setup() method is pulling data from an API to populate the page. It works fine when the route is directly loaded, but when I change the route to a different page slug (eg. clicking a router-link), the component is not reloaded and setup is not executed again to fetch the new data.
I guess I somehow want to force reloading the component?
This is my MainApp component it has the router view and fallback.
<router-view v-slot="{ Component }">
<Suspense>
<template #default>
<component :is="Component" />
</template>
<template #fallback>
loading...
</template>
</Suspense>
</router-view>
The router looks kinda like that. You see the page component takes a page_slug:
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "",
component: MainApp,
children: [
{
name: "page",
path: "page/:page_slug",
component: Page,
props: true,
},
// [...]
]
}
And this is how my Page component looks like. It uses the page_slug to load data from an API which is then used in the template:
<template>
<div> {{ pageData }} </div>
</template>
export default defineComponent({
name: "Page",
props: {
page_slug: {
type: String,
required: true,
},
},
async setup(props) {
const pageData = await store.dispatch("getPageData", {
page_slug: props.page_slug
});
return { pageData }
}
}
When I directly open the route, the fallback "loading..." is nicely shown until the data is returned and the component is rendered.
But when I do a route change to another page, then async setup() is not executed again. In that case the url in the browser updates, but the data just remains the same.
How can I solve this case? Do I have to force reload the component somehow? Or have an entirely different architecture to the data loading?
The answer is simple, when trying to create Vue 3 Single File Components (SFCs) in Composition API way as shown below:
<template>
<!-- Your HTML code-->
</template>
<script>
export default {
name: 'ComponentName',
async setup():{
// Your code
}
};
</script>
<style>
/*Your Style Code*/
</style>
<script>, will only executes once when the component is first imported. So, when the data have changed by other component, the component above will not updated or in other words not re-created.
To make your component re-created whenever it about to mount, you have to use <script setup> which will make sure the code inside will execute every time an instance of the component is created, but you need to re-write your script code with few changes in comparison when using setup() method, and also you are able to use both of scripts like this:
<script>
// normal <script>, executed in module scope (only once)
runSideEffectOnce()
// declare additional options
export default {
name: "ComponentName",
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
// executed in setup() scope (for each instance)
</script>
Read this documentation carefully to have full idea.

Bootstrap image asset not processed for props style attribute on Vue Component (Nuxt.js)

I have been trying to import an image asset relative path to a banner component. The following works just fine.
<b-img src="~/static/images/carousel1.jpg" alt="Samyojya Consultants banner"/>
On html, I see it rendered as this
<div class="card-body"><img src="/_nuxt/static/images/carousel1.jpg"...
But the v-bind style representation like this does not bundle the image
<b-img :src="imgSrc" :alt="title+'banner'"/>
I can see on the html that imgSrc value is passing on but not compiled by asset processor
<div class="card-body"><img src="~/static/images/carousel1.jpg" ...
Is there a way we can explicitly trigger this compilation? require doesn't seem to work too.
<b-img :src="require(imgSrc)" :alt="require(title)+'banner'"/>
This dynamic style is needed for my use-case.
Create a computed prop (or method, or similar) to resolve (require) the relative path:
export default {
data() {
return {
title: 'Image title'
}
},
computed: {
imgSrc() {
// Relative to component directory
return require('./image.png')
}
}
}
And then reference that in your template:
<b-img :src="imgSrc" :alt="title+' banner'"/>
On the calling (parent) template, I used this
<banner :imgSrc="imgSrc" ...
And the data export in parent like this.
export default {
data: function(){
return {
imgSrc:require('../static/images/carousel2.jpg')
}
},
...
In the child component where the banner is drawn.
<b-img :src="imgSrc"...
Note: require needs a relative path (../static) from components/pages while without require we can use absolute (~/static).
<b-img :src="require('../static/images/carousel1.jpg')" alt="Samyojya Consultants banner"/>

How to place component in the header with lazy loading?

In my vue application, how to place component (slot?) in the toolbar component?
My app for example:
<template>
<toolbar>…</toolbar>
<router-view />
</template>
and all the routes are lazy loaded.
for some routes I want to place component inside toolbar component. But I can't "insert" the component as slot. and to write the component and turn on/off with v-if seems to me wrong.
I think that I expect is
<div for="toolbar">This content should in toolbar</div>
<div for="router-view">This content for router-view</div>
Is there any way to solve this?
Vue Router Named Views will come in handy.
Sometimes you need to display multiple views at the same time instead
of nesting them, e.g. creating a layout with a sidebar view and a main
view. This is where named views come in handy. Instead of having one
single outlet in your view, you can have multiple and give each of
them a name. A router-view without a name will be given default as its
name.
A view is rendered by using a component, therefore multiple views require multiple components for the same route. Make sure to use the components (with an s) option:
<template>
<toolbar><router-view name="toolbar"></router-view></toolbar>
<router-view />
</template>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: YourAwesomeComponent,
toolbar: YetAnotherAwesomeComponent
}
},
{
path: '/home',
components: {
default: YourAwesomeHomeComponent,
toolbar: YetAnotherAwesomeComponentThatSouldBeInToolbarOnHomePage
}
}
]
})

How to create a reusable component in VueJs?

I would like to create Side Panel as a reusable component in Framework7 with VueJS. Here is my code..
Card.vue
<f7-card>
<f7-card-header>Card header content</f7-card-header>
<f7-card-content><img src="https://wiki.videolan.org/images/Ubuntu-logo.png"></img></f7-card-content>
<f7-card-footer>Card footer content</f7-card-footer>
</f7-card>
Now i registered as a component like below,
import Vue from 'vue'
export default [
{
path: '/card/',
component: require('./Card')
},
]
In later vues i imported as,
import Card from './Card.vue'
and i try to access in another vues like,
Now i'm getting an error like
[Vue warn]: Unknown custom element: - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
Can anyone help me where have i mistaken?
Thanks,
It's not really clear from your code exactly what you are doing, but the error you are getting happens when you try to use a component as a child of another component without registering it in the parent's components setting like this:
<script>
import Card from './Card.vue'
export default {
data () {
return {
somedata: 'some value'
}
},
components: {Card: Card}, // <- you're missing this
// etc...
}
</script>
You can also register components globally. More here: https://v2.vuejs.org/v2/guide/components.html#Local-Registration
Are you showing us all of Card.vue? For a valid single-file vue component, I would expect to see <template>, <script> and <style> elements. The render function will be generated from whatever you put in the <template> element.
Make sure that the component that you want to reuse is wrapped inside a template tag
As follows
<template>
<div>
<component data/>
<div/>
<template/>
Then register it inside the parent
Like so
export default {
name: "Card",
components: {
Card
},
};