In vuejs 2 I was able to render directly in the router by return a render html tag. I'm trying to do the same things in vue3 with vue-router 4, but it doesn't appear to work:
{
path: 'posts',
redirect: '/posts/all',
name: 'posts',
meta: {'breadcrumb': 'Posts'},
component: {
render() {
return h('router-view');
}
},
children: [ //Lots of sub routes here that I could load off the anonymous router-view]
}
Any idea how I can get the component to render that router-view and allow me to use my children routes? I rather not load up a single component just to house "". This worked perfectly in vue-router 3, no idea what is wrong with 4. I also am importing the {h} from 'vue'.
From the Vue 3 Render Function docs:
In 3.x, with VNodes being context-free, we can no longer use a string ID to implicitly lookup registered components. Instead, we need to use an imported resolveComponent method:
You can still do h('div') because it's not a component, but for components you have to use resolveComponent:
import { h, resolveComponent } from Vue; // import `resolveComponent` too
component: {
render() {
return h(resolveComponent('router-view'))
}
},
Alternatively, if you wanted to use the runtime compiler, you could use the template option (not recommended because the compiler increases app size):
component: {
template: `<router-view></router-view>`
}
Since Vue Router 4, you can use the RouterView component directly:
import { RouterView } from 'vue-router';
//...
{
path: 'posts',
redirect: '/posts/all',
name: 'posts',
meta: {'breadcrumb': 'Posts'},
component: RouterView, // <- here
children: [ /* ... */ ]
}
Source: https://github.com/vuejs/vue-router/issues/2105#issuecomment-761854147
P.S. As per linked issue, there are allegedly some problems with <transition> when you use this technique, so use caution.
Related
The ask
Control a root-level view from a child route. The example below is one level deep, but I could need to control the other view from deeper routes.
Have both the default view and another view in a single template, other simply must live at this level and will not work how I need if nested within other components:
<router-view name="default"/>
<router-view name="other">/>
Must have a parent-child route relationship to maintain navigation functionality (router-link-active)
Attempts
For a route, have the view set:
{
path: '/my-path',
component: ComponentOne,
}
Going to /my-path shows ComponentOne in default view, other view is empty, as it should.
Now at a child route, I want the other view to show ComponentTwo.
This does not work because it expects other view to be within default view, and so ComponentTwo is simply not rendered:
{
path: '/my-path',
component: ComponentOne,
children: [
{
path: '/my-path/more',
components: {
'other': ComponentTwo
}
}
]
}
This does not work because it does not maintain the parent-child relationship:
{
path: '/my-path',
component: ComponentOne
},
{
path: '/my-path/more',
components: {
default: ComponentOne,
other: ComponentTwo
}
}
I don't think it's possible to control a router-view from a nested route.
However, to do what you ask quickly, the solution would be to manage the contents of all (/my-path and sub-routes), in a nested route even for the path /my-path as well. You don't even need named routes.
I'll explain better with an example:
{
path: '/my-path',
component: ComponentWrapper,
children: [
{
path: '', // Match '/my-path' or '/my-path/'
component: ComponentOne
},
{
path: 'more', '/my-path/more'
component: ComponentTwo
}
]
}
In the ComponentWrapper you have to insert a router-view to render the nested routes and everything will work fine, even parent-child route relationship.
Unfortunately Leonardi's solution does not work if you wanted to refer to the the current child's component from a component that's more than 1 level higher in the hierarchy (i.e. a layout).
I was able to work around this by writing a computed method to determine the component of the current child route in the parent component.
{
path: '/my-path',
component: ComponentOne,
children: [
{
path: '/my-path/more',
components: {
'other': ComponentTwo
}
}
]
}
<!-- App.vue or SomeLayout.vue -->
<template>
<router-view /> <!-- ComponentOne -->
<component :is="componentOfCurrentChildRoute" /> <!-- ComponentTwo -->
</template>
<script>
export default {
computed: {
componentOfCurrentChildRoute() {
// Get an array of the current route ancestor hierarchy
const {matched} = this.$route;
// Return the component defined by the current child route
return matched?.[matched.length - 1]?.component; // or components.sidebar if using named views
}
}
}
</script>
I'd like to conditionnaly import a component in the vue router. Here is what I have for the moment:
children: [
{
path: ':option',
component: () => import('../components/Option1.vue'),
},
],
Depending on what :option is, I want to import a different component (Option1.vue, Option2.vue, etc.). I know I could put several children but i actually need the option variable in my parent component (I make tests if the route has an option).
How would it be possible to do that?
Thanks in advance :)
You can create a loader component containing a dynamic component instead of doing conditional routing. In the loader, you'll conditionally lazy load the option component based on the route param. Not only is this easier when routing, you also don't have to manually import anything, and only options that are used will be imported.
Step 1. Route to the option loader component
router
{
path: ':option',
component: () => import('../components/OptionLoader.vue'),
}
Step 2. In that option loader template, use a dynamic component which will be determined by a computed called optionComponent:
OptionLoader.vue
<template>
<component :is="optionComponent" />
</template>
Step 3. Create a computed that lazy loads the current option
OptionLoader.vue
export default {
computed: {
optionComponent() {
return () => import(`#/components/Option${this.$route.params.option}.vue`);
}
}
}
This will load the component called "Option5.vue", for example, when the option route param is 5. Now you have a lazy loaded option loader and didn't have to manually import each option.
Edit: OP has now indicated that he's using Vue 3.
Vue 3
For Vue 3, change the computed to use defineAsyncComponent:
OptionsLoader.vue
import { defineAsyncComponent } from "vue";
computed: {
optionComponent() {
return defineAsyncComponent(() =>
import(`#/components/Option${this.$route.params.option}.vue`)
);
}
}
Here is something that works in VueJS3:
<template>
<component :is="userComponent"/>
</template>
<script>
import { defineAsyncComponent } from 'vue';
import { useRoute, useRouter } from 'vue-router';
export default {
computed: {
userComponent() {
const route = useRoute();
const router = useRouter();
const components = {
first: 'Option1',
second: 'Option2',
third: 'OtherOption',
fourth: 'DefaultOption',
};
if (components[route.params.option]) {
return defineAsyncComponent(() => import(`./options/${components[route.params.option]}.vue`));
}
router.push({ path: `/rubrique/${route.params.parent}`, replace: true });
return false;
},
},
};
</script>
Source: https://v3-migration.vuejs.org/breaking-changes/async-components.html
And it's possible to get an error message like this one for the line with "return":
Syntax Error: TypeError: Cannot read property 'range' of null
In that case, it means you probably want to migrate from babel-eslint to #babel/eslint-parser (source: https://babeljs.io/blog/2020/07/13/the-state-of-babel-eslint#the-present)
I'm setting up what is going to be a large system, so I want to have decentralised router.js files for every module. Each of them would have routes arrays declarations that would be pushed to the main routes array.
So for now I have a kind of root of routes file, which have the VueRouter instances and settings, and it imports the first level of route.js files to build the routes array.
main routes.js file
import DashboardRoutes from './views/dashboard/routes'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{// Root public site
path: '/',
component: () => import('./views/pages/Index'),
children: PagesRoutes
},
name: 'page_error',
path: '*',
component: () => import('./views/error/Error')
}
].concat(
DashboardRoutes
)
})
module (first level) routes.js file
import InventoryRoutes from './inventarios/routes'
const routes = {
path: '/panel',
component: () => import('./Index'), // This Index has a layout with a router-view in it
children: [
// Inicio del panel
{
name: 'dashboard',
path: '',
component: () => import('./Dashboard'),
}
].concat(
InventariosRoutes
)
}
export default routes
Components (second level) routes.js file
const routes = {
path: 'inventario',
name: 'panel_inventario',
component: { template: '<router-view/>' },
children: [
// Productos y servicios
{
name: 'panel_inventarios_productos-servicios',
path: 'productos-servicios',
component: () => import('./productos-servicios/ProductosServiciosHome'),
},
]
}
export default routes
In the components tree of vue-tools I see an AnnonymousComponent in the place where my children's router-view should be.
Update
I just create an external component to name it and check if it's being rendered like this
Components (second level) routes.js file
const loader = {
template: `
<div class="InventarioLoader">
<router-view></router-view>
</div>
`,
name: 'InventarioLoader'
}
const routes = {
path: 'inventario',
name: 'panel_inventario',
component: loader,
children: [
// children go here
]
}
Now I see my InventarioLoader component, but I still don't see my children components rendered in it
Update
I see this error on the console
You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
By default, Vue does not compile string templates. This is for performance reasons.
Since the runtime-only builds are roughly 30% lighter-weight than their full-build counterparts, you should use it whenever you can
See https://v2.vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
Either create a real single-file component or use a render function
import Vue from 'vue'
const RouterView = Vue.component('router-view')
const loader = {
name: 'InventarioLoader',
render (createElement) {
return createElement(RouterView)
}
}
Apparently now you can't point a route to a inline component like {template:'<router-view/>} and my solution was to create a loader component with that template and use it on the parent route
I use vue-router likeļ¼
export default new Router({
routes: [
{
path: '/',
components: {
navbar: Navbar,
subnavbar: SubNavbar,
main: WhoisPage //********
}
}
In my component WhoisPage.vue, I want to know the component path or the main module(which is //****** line) use which component?
How to do it with vue or vuex?
First of all, there is no "components" property on vue-router.
You need to use "component" property to inject your shell component (in this case, it looks like WhoisPage) then you can call your sub-components to build your page inside your shell component.
If you want to change your sub-components based on route automatically, you can send their names as string and render in your shell-component.
// in your router config.
export default new Router({
routes: [
{
path: '/',
component: WhoisPage,
meta: {
navbar: 'NavbarComponent'
}
}
]
})
<!-- in your shell component, ofc your navbar needs to be imported -->
<component :is="$route.meta.navbar"></component>
Not sure i correctly answered what you need tho.
I'm having trouble getting my child views to render in Vue.
My main.js file looks like this
import DashboardProducts from './components/Dashboard/DashboardProducts'
import DashboardSettings from './components/Dashboard/DashboardSettings'
Vue.use(VueRouter)
Vue.use(Vuex)
const routes = [
{ path: '/activate', component: Activate },
{ path: '/dashboard/:view', component: Dashboard,
children: [
{ path: 'products', component: DashboardProducts },
{ path: 'settings', component: DashboardSettings }
]
},
{ path: '/login', component: Login },
{ path: '/account', component: UserAccount }
];
const router = new VueRouter({
routes // short for routes: routes
});
export default router;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
render: h => h(App)
});
As you can see I have imported the components and get no errors. I have also added them as children of Dashboard and set their paths.
In my Dashboard.vue view I do this
<template>
<div>
<dashboard-nav></dashboard-nav>
<!-- Will display product and settings components -->
<router-view></router-view>
</div>
</template>
<script>
import DashboardNav from '../components/Dashboard/DashboardNav'
export default {
name: 'Dashboard',
components: {
DashboardNav
}
};
</script>
<style>
</style>
Urls are matching but no components are rendering. What am I missing?
Here is a JSFiddle of pretty much what I'm going for https://jsfiddle.net/dtac5m11/
It seems to be working fine there but I'm also using single file components in my app so it may be a little different?
Again, the issue is getting the child components to render when their routes match. Currently no components are being mounted.
UPDATE:
I am getting the DashboardProducts component to render but can't get DashboardSettings to render.
Thanks!
{ path: '/dashboard/:view', component: Dashboard,
At first, for what purpose do you add :view after dashboard path? If you are using this one for children path as a parameter, it is an issue. It is the reason, why your children component are not rendering. Because, :view is for dynamic routes. /dashboard/:view is equivalent to /dashboard/* and it means that after /dashboard there can be any route and this route will render Dashboard component. And your children paths /dashboard/products and /dashboard/settings will always match /dashboard/:view and render parent component-Dashboard.
So, in your case, your routes for children components are known. So you do not need to use :view.
More, https://router.vuejs.org/en/essentials/dynamic-matching.html.