Vue-router: Expose child routes on currentRoute object

Created on 9 Feb 2017  路  16Comments  路  Source: vuejs/vue-router

Any issue with adding the route children to the $router.currentRoute object? Adding routes dynamically might make this tricky, maybe vuex-router-sync would be more appropriate for this.
I'm not sure how to currently access nested routes without doing a find in the complete routes array.

Access to the current routes children would allow us to easily list child routes in a sub-navigation menu.

Ex:

computed: {
  children: () => this.$router.currentRoute.children
  }
}
...
<nav class="sub-nav">
  <router-link
    v-for="child in children"
    :to="{name: child.name}"
    >
    {{ child.meta.label }}
  </router-link>
</nav>

<router-view></router-view>
feature request has workaround needs RFC

Most helpful comment

Would there be any solution for this? It's such a long time.

All 16 comments

Closing in favour of #1156

@posva These issues don't seem the same

I am requesting adding the predefined child routes to the currentRoute, not dynamically adding routes

Sorry for that. Adding all children routes to the currentRoute looks like a handy hack for your case but I'd rather just export them and import where necessary. @fnlctrl WDYT?

Currently, it should be possible to access the children routes records with:

$route.matched[0].children

I think it seems reasonable for this use case.. Updated the title to be less ambiguous.

So what is the current progress with this ? About half an year passed ...

Would there be any solution for this? It's such a long time.

Any news regarding this feature request? 馃槉

I'd also like to give a vote for this feature.
Being able to dynamically generate navigation is great!

Any change, news or novelty of this subject?.

I would like this as well.

Given a route that has children, it would be nice to generate a nav knowing what children routes are available.

edit: A workaround is basically like this:

computed: {
  routeChildren() {
    const matched = this.$route.matched;
    const routePath = matched[matched.length - 1].path;
    return this.getChildrenByPath(routePath);
  },
  routeSiblings() {
    const matched = this.$route.matched;
    const route = matched[matched.length - 1];
    return this.getChildrenByPath(route.parent
      ? route.parent.path
      : undefined);
  },
},
methods: {
  getChildrenByPath(path) {
    let children = this.$router.options.routes;

    if (path) {
      // clean up empty elements
      let chunks = path.split('/');
      chunks = chunks.filter((chunk) => chunk !== '');

      if (chunks.length) {
        chunks.forEach((chunk, index) => {
          let path = chunk;
          if (index === 0) path = `/${path}`;

          if (children) {
            const found = children.find((child) => child.path === path);
            if (found) {
              children = found.children;
            }
          }
        });
      }
    }

    return children;
  },
},

@sysebert
the workaround suggestd doesn't work with a route config like this one :

import { RouteConfig } from 'vue-router';
import { RouteName } from 'src/constants/route-name';

export type MyRouteConfig = {
  name?: RouteName;
  children?: MyRouteConfig[];
} & Omit<RouteConfig, 'name'|'children'>;

const routes: MyRouteConfig[] = [
  {
    path: '/login',
    component: () => import('layouts/LoginLayout.vue'),
    children: [
      { path: '', component: () => import('pages/Login.vue') }
    ],
  },
  {
    path: '/',
    component: () => import('layouts/MainLayout.vue'),
    children: [
      { path: '', component: () => import('pages/Home.vue') },
      { path: 'page-a', component: () => import('pages/Page-a.vue') },
      {
        path: 'page-b',
        component: routerViewVue,
        children: [
          { path: '', component: () => import('pages/Page-b.vue') },
          {
            path: 'page-c',
            component: routerViewVue,
            children: [
              { path: '', component: () => import('pages/Page-c.vue') },
              {
                path: 'page-d/:id',
                component: () => import('layouts/page-d.vue'),
                children: [
                  { path: '', name: RouteName['name-1'], redirect: 'page-e' },
                  {
                    name: RouteName[''],
                    path: 'page-e',
                    component: () => import('layouts/layout-2.vue'),
                    children: [
                      { name: RouteName['name-2'], path: 'page-f', component: () => import('pages/page-f.vue') },
                      { name: RouteName['name-3'], path: 'page-g', component: () => import('pages/page-g.vue') },
                      { name: RouteName['name-4'], path: 'page-h', component: () => import('pages/page-h.vue') },
                      { name: RouteName['name-5'], path: 'page-i', component: () => import('pages/page-i.vue') },
                      { name: RouteName['name-6'], path: 'page-j', component: () => import('pages/page-j.vue') },
                      { name: RouteName['name-7'], path: 'page-k', component: () => import('pages/page-k.vue') },
                    ]
                  },
                  {
                    name: RouteName['name-8'],
                    path: 'page-l/:id',
                    component: () => import('layouts/layout-3.vue'),
                    children: [
                      { name: RouteName['name-9'], path: 'page-m', component: () => import('pages/page-m.vue') },
                      { name: RouteName['name-10'], path: 'page-n', component: () => import('pages/page-n.vue') },
                      { name: RouteName['name-11'], path: 'page-o', component: () => import('pages/page-o.vue') },
                      { name: RouteName['name-12'], path: 'page-p', component: () => import('pages/page-p.vue') },
                      { name: RouteName['name-13'], path: 'page-q', component: () => import('pages/page-q.vue') },
                      { name: RouteName['name-14'], path: 'page-r', component: () => import('pages/page-r.vue') },
                      { name: RouteName['name-15'], path: 'page-s', component: () => import('pages/page-s.vue') },
                      { name: RouteName['name-16'], path: 'page-t', component: () => import('pages/page-t.vue') },
                      { name: RouteName['name-17'], path: 'page-u', component: () => import('pages/page-u.vue') },
                      { name: RouteName['name-18'], path: 'page-v', component: () => import('pages/page-v.vue') },
                      { name: RouteName['name-19'], path: 'page-w', component: () => import('pages/page-w.vue') },
                      { name: RouteName['name-20'], path: 'page-x', component: () => import('pages/page-x.vue') },
                      { name: RouteName['name-21'], path: 'page-y', component: () => import('pages/page-y.vue'), props: true },
                      { name: RouteName['name-22'], path: 'page-z', component: () => import('pages/page-z.vue') },
                      { name: RouteName['name-23'], path: 'page-aa', component: () => import('pages/page-aa.vue') },
                      { name: RouteName['name-24'], path: 'page-ab', component: () => import('pages/page-ab.vue') },
                    ]
                  }
                ]
              }
            ]
          }
        ]
      },
      { path: 'page-ac', component: () => import('pages/page-ac.vue') },
      { path: 'page-ad', component: () => import('pages/page-ad.vue') },
    ],
  },
];

export default routes;

I had this problem as well, because I had a nav that rendered parent routes and I wanted children routes to keep the selection on the parent in the nav when they were traveled to.

If this is your case or you are facing a similar problem, meta can help you out.

I just added a meta to my child route, selected_if_route_is: 'parent_route'

@sysebert
I adapted your workaround to make it work with my routes.

import VueRouter, { RouterOptions, RouteConfig } from 'vue-router';

type MyVueRouter = VueRouter & { options: RouterOptions };

export const extensions = function (router: VueRouter)
{
  const routes = (router as MyVueRouter).options.routes;

  function getCurrentRoute(path: string | undefined, children: RouteConfig[] | undefined): RouteConfig | null {
    if (path && children) {
      for(let child of children) {
        if (path.length === 0 && child.path.length === 0) {
          return child;
        } if(path.startsWith(child.path)) {
          let index = child.path.length;
          if (child.path !== '/') {
            index++; // remove the '/' at the end
          }
          const subPath = path.substring(index);

          // we reach the end of the path to resolved
          if (subPath.length === 0) {
            return child;
          } else if(child.children !== undefined) {
            const found = getCurrentRoute(subPath, child.children);
            if (found) {
              return found;
            }
          }
        }
      }
    }
    return null;
  }

  return {
    routeChildren: () => {
      const matched = router.currentRoute.matched;
      const routePath = matched[matched.length - 1].path;
      return getCurrentRoute(routePath, routes)?.children;
    },
    routeSiblings: () => {
      const matched = router.currentRoute.matched;
      const route = matched[matched.length - 1];
      return getCurrentRoute(route.parent
        ? route.parent.path
        : undefined, routes)?.children;
    }
  };
};

I found my way to get the siblings routes from current route to render my navbar / drawer:

<template>
  <div>
    <div>
      <h1>Sibling paths from Current Route</h1>
      <h2>Current path: {{ current_route_path }}</h2>
      <h2>Sibling paths:</h2>
      <ul>
        <li v-for="item in items" :key="item.path">
          route "{{ item.path }}" named as "{{ item.menu }}"
        </li>
      </ul>
    </div>
    <router-view />
  </div>
</template>

<script>
export default {
  methods: {
    addSiblings(current_route, root_path, result_array, routes_node) {
      var array_sibling = []
      var founded = false
      routes_node.forEach(route => {
        var full_path = root_path + (root_path === '' ? '' : '/') + route.path

        founded = founded || full_path === current_route

        // fill array at this level 'cause maybe was our goal level
        array_sibling.push({
          path: full_path,
          menu: route.meta?.menu //added in Route Meta Field -> see: https://router.vuejs.org/guide/advanced/meta.html#route-meta-fields
        })

        if (
          !founded &&
          route.children &&
          current_route.indexOf(full_path) == 0
        ) {
          this.addSiblings(
            current_route,
            full_path,
            result_array,
            route.children
          )
        }
      })
      if (founded) {
        result_array.push(...array_sibling)
      }
    }
  },
  computed: {
    current_route_path: function() {
      return this.$route.path
    },
    items: function() {
      var arr = []
      this.addSiblings(
        this.current_route_path,
        '',
        arr,
        this.$router.options.routes
      )
      return arr
    }
  }
}
</script>

This will be achievable by getting the Route Record from router.getRoutes after https://github.com/vuejs/vue-router/issues/2940 is implemented

I'm using the below to loop over the children array and also get any children of children(sub).
You can go as far down as you want with this. It's just loops within loops.

This is being used to dynamically generate a sidebar menu based on my child routes. I'm also hiding the values I don't want by setting showInSidebar: false on each route I want to hide. It's working pretty well, but exposing the full children and children of children, etc, as well as any custom properties (without using a props object, or a meta object) would be really helpful. Just give us all the things, vue-router!

Can't remember where I found it, but for others I'll leave my current solution here:

<!-- GET CHILDREN OF CURRENT ROUTE -->
    <div
      v-for="(route, idx) in $router.options.routes.filter(
        (routeItem) => routeItem.name == $route.matched[0].name
      )"
      :key="idx"
    >
      <!-- CHILD ROUTES (of current main route) -->
      <div v-for="child in route.children" :key="child.name" class="nav-item">
        <!-- Show in sidebar if showInSidebar prop is not false -->
        <router-link
          v-if="child.showInSidebar !== false"
          :to="{ name: child.name }"
          exact-active-class="active"
        >
          <i /><span>{{ child.title }}</span>
        </router-link>
        <!-- SUB ROUTES (if they exist), and if showInSidebar prop is true -->
        <div v-show="child.children && child.children.showInSidebar">
          <div
            v-for="sub in child.children"
            :key="sub.name"
            class="ml-1 nav-item"
          >
            <router-link :to="{ name: sub.name }" exact-active-class="active">
              <i class="mr-2" /><span>{{ sub.title }}</span>
            </router-link>
          </div>
        </div>
      </div>
    </div>
Was this page helpful?
0 / 5 - 0 ratings