vuejs/vue-router

Empty parent in nested routes

rayfranco opened this issue Β· 23 comments

It could be interesting to have a parent route definition without any component and having the matched children rendered in the root <router-view>. Here is an example:

var routes = [
  {
    path: '/:lang',
    children: [
      {
        path: '',
        component: WelcomeComponent
      },
      {
        path: 'hello/:name',
        component: HelloComponent
      }
    ]
  }
]

This won't work. Instead, this is what I am doing:

var routes = [
  {
    path: '/:lang',
    // create a container component
    component: {
      render (c) { return c('router-view') }
    },
    children: [
      {
        path: '',
        component: WelcomeComponent
      },
      {
        path: 'hello/:name',
        component: HelloComponent
      }
    ]
  }
]

But the original problem was to scope my routes by their definitions only, not by components.

+1 for this feature.

Even more, is it possible to assign a parent component in child route declaration?

For example, a parent route have ten children routes, eight have same ui part(same footer or header), two are different(without footer or header).

In this case, we need break the same part from parent view, add to these eight route views, otherwise the two other route views would have same part too, which is not our purpose.

@micate

Even more, is it possible to assign a parent component in child route declaration?

Yes.

For example, a parent route have ten children routes, eight have same ui part(same footer or header), two are different(without footer or header).

I guess you can try named views for this http://router.vuejs.org/en/essentials/named-views.html

I think this breaks the relationship between route config nesting and router-view nesting and can make things a bit harder to reason about.

You might be able to do something like:

prefixWith('/:lang', [
  {
    path: '',
    component: WelcomeComponent
  },
  {
    path: 'hello/:name',
    component: HelloComponent
  }
])

where prefixWith's implementation should be pretty straightforward.

@yyx990803 Where can I find the doc or an example on how to use prefixWith? I guess I dont know where the prefixWith goes in the code. Thank you!

posva commented

It's simply a function that rewrites the array to prepends /:lang to all routes provided. It's not documented anywhere, you shall write it yourself πŸ˜„

+1 for documentation help?

kinda shitty that you can't just omit the Component when you want the child to be rendered instead of the parent...

My take on this:

const withPrefix = (prefix, routes) => 
    routes.map( (route) => {
        route.path = prefix + route.path;
        return route;
    });

export const routes = [
    { 
        path: '/',
        component: Home 
    },
    { 
        path: '/contacts',
        component: Contacts,
        props:{
            pageTitle: 'Contacts'
        }
    },
    ...withPrefix('/settings',[
        { 
            path: '/account',
            component: AccountSettings,
            props:{
                pageTitle: 'Account settings'
            },
        },
        { 
            path: '/application',
            component: AppSettings,
            props:{
                pageTitle: 'Application settings'
            },
        },
    ]),
]

Pretty straightforward, although I can understand the desire for a utility function for this.

@dschreij πŸ‘

I'm using redirect to jump parent component

{
      path: '/anime',
      redirect: '/anime/home',
      name: 'Anime',
      component: Anime,
      meta: {
        humanName: 'Animes',
        showOnNav: true
      },
      children: [
        {
          path: 'home',
          name: 'AnimeHome',
          component: AnimeHome,
          meta: {
            humanName: 'List'
          },
        },
        {
          path: 'new',
          name: 'AnimeNew',
          component: AnimeNew,
          meta: {
            humanName: 'New'
          },
        },
        {
          path: 'edit/:id',
          name: 'AnimeEdit',
          component: AnimeEdit,
          meta: {
            humanName: 'Edit'
          },
        }
      ],
 },

@Rubensfernandes But you define parent component. Did you read the OP?

To me it is not at all unreasonable to assume that given the following routes, accessing accessing /prefix would render the Home component.

[
 path: '/prefix'
 children: [
   {path: '/', component: Home}]
]

Especially because in the docs, component: is listed as optional in the route config parameters and also because the mapping method (appending prefix to each route) seems just as likely to 'not work' because declaring routes with slashes in the middle is a little bit irregular (though logical once inside the regex POV)

withPrefix is a good solution in many situations, but it does not allow setting a beforeEnter on a parent for purposes such as auth checks. Here's an example:

{
    path: 'users',
    component: Users,
    beforeEnter (to, from, next) {
        next(notAdminRedirect('jobs'))
    },
    children: [
        {
            path: '',
            name: 'user-list',
            component: UserList,
        },
        {
            path: 'create',
            name: 'user-create',
            component: UserForm,
        },
        {
            path: ':userId',
            component: User,
            children: [
                {
                    path: '',
                    name: 'user-view',
                    component: UserView,
                },
                {
                    path: 'edit',
                    name: 'user-edit',
                    component: UserForm,
                },
            ],
        },
    ]
}

So in this example, all child paths of users are checked with my notAdminRedirect() function. However, if we used the withPrefix suggestion, we'd end up copying the beforeEnter code to every route.

Cmacu commented

This should be reopened or the API reference should list the component as required.
The optional component makes it look like this is possible and when it doesn't work (no errors either) it becomes confusing.

Its surprising to me there isn't a out of the box way in Vue router to group routes by a common route prefix without the need to define a dummy parent component. imo this is an intuitive feature and leads to more confusion when it doesn't work as expected.

Even if it is annoying, i use this as a feature. Layouts makes jumpy views. I use parent component as layout.
πŸ’―

My take, which autodetect if a logical level has been defined and automatically flat it.

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

function shouldFlatRoute(route) {
  return route.path && route.children && !route.component;
}

function applyPrefix(prefix, routes) {
  return routes.map(route => {
    route.path = prefix + '/' + route.path;
    return route;
  });
}

function flatRoutes(routes) {
  const flattenedRoutes = [];

  for (const route of routes) {
    if (!route.children || route.children.length === 0) {
      flattenedRoutes.push(route);
    } else {
      if (shouldFlatRoute(route)) {
        // Flat the logical nesting and call recursion
        flattenedRoutes.push(
          ...flatRoutes(applyPrefix(route.path, route.children)),
        );
      } else {
        // Keep the nesting level but call recursion on children
        route.children = flatRoutes(route.children);
        flattenedRoutes.push(route);
      }
    }
  }

  return flattenedRoutes;
}

const routes = [ /*... */ ];

const router = new VueRouter({
  routes: flatRoutes(routes),
});
oles commented

@posva any thoughts on this one?

I'd like to see it re-opened, as I recently hit this problem again, which again brought me to this issue.

It also feels really buggy. In this example, when I navigated to /en/, the page was just blank, with no error messages.

export default [

    {
        name: 'language-select',
        path: '/',
        component: language_select
    },

    {
        path: '/:locale/',
        children: [
            {
                path: '/',
                name: 'home',
                component: home
            },

            {
                name: 'order',
                path: '/order/:key',
                component: order
            },


            // ... more pages
        ]
    },

    {
        name: '404',
        path: '*',
        component: not_found
    }

]

My take on this:

const withPrefix = (prefix, routes) => 
    routes.map( (route) => {
        route.path = prefix + route.path;
        return route;
    });

export const routes = [
    { 
        path: '/',
        component: Home 
    },
    { 
        path: '/contacts',
        component: Contacts,
        props:{
            pageTitle: 'Contacts'
        }
    },
    ...withPrefix('/settings',[
        { 
            path: '/account',
            component: AccountSettings,
            props:{
                pageTitle: 'Account settings'
            },
        },
        { 
            path: '/application',
            component: AppSettings,
            props:{
                pageTitle: 'Application settings'
            },
        },
    ]),
]

Pretty straightforward, although I can understand the desire for a utility function for this.

When I need to /settings/account what path should I give in router-link "to" attribute ?

We created a package to solve this issue, if you want to try it out
https://github.com/dreamonkey/vue-routes-flattener

At first I thought that is a default behavior of Vue's Router.
Thanks @IlCallo for your pckg.
Hope in the future a kind of separation of logical routes will be integrated into the VueRouter.
Or maybe something on the lines of a route parent/child property "no-router-view"/"replace".