Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose child routes on currentRoute object #1149

Closed
JoshuaDoshua opened this issue Feb 9, 2017 · 17 comments
Closed

Expose child routes on currentRoute object #1149

JoshuaDoshua opened this issue Feb 9, 2017 · 17 comments
Labels
feature request has workaround needs RFC This feature request needs to go through the RFC process to gather more information
Projects

Comments

@JoshuaDoshua
Copy link

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>
@posva
Copy link
Member

posva commented Feb 11, 2017

Closing in favour of #1156

@posva posva closed this as completed Feb 11, 2017
@JoshuaDoshua
Copy link
Author

@posva These issues don't seem the same

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

@posva posva reopened this Feb 13, 2017
@posva
Copy link
Member

posva commented Feb 13, 2017

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

@fnlctrl fnlctrl changed the title Possible to add child routes to currentRoute object? Expose child routes on currentRoute object Feb 14, 2017
@fnlctrl
Copy link
Member

fnlctrl commented Feb 14, 2017

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

@tmcdos
Copy link

tmcdos commented Jul 27, 2017

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

@NoraGithub
Copy link

NoraGithub commented May 15, 2018

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

@vuejs vuejs deleted a comment from rchipka Aug 15, 2018
@vuejs vuejs deleted a comment from rannooo Dec 6, 2018
@vuejs vuejs deleted a comment from b4dnewz Feb 19, 2019
@Jo-Hansen
Copy link

Any news regarding this feature request? 😊

@posva posva added this to Doable or in refactor (low prio, low complex) in Longterm Mar 26, 2019
@MrAdam
Copy link

MrAdam commented May 27, 2019

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

@Gonzalo2310
Copy link

Any change, news or novelty of this subject?.

@sysebert
Copy link

sysebert commented Aug 13, 2019

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;
  },
},

@Davilink
Copy link

@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;

@maxKimoby
Copy link

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'

@Davilink
Copy link

Davilink commented Feb 6, 2020

@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;
    }
  };
};

@alitecsolucoes
Copy link

alitecsolucoes commented Apr 26, 2020

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>

@posva posva added the needs RFC This feature request needs to go through the RFC process to gather more information label May 26, 2020
@posva posva moved this from Doable or in refactor (low prio, low complex) to Design proposals + discussion (high prio, high complex) in Longterm May 26, 2020
@posva
Copy link
Member

posva commented Jun 24, 2020

This will be achievable by getting the Route Record from router.getRoutes after #2940 is implemented

@posva posva closed this as completed Jun 24, 2020
@posva posva moved this from Design proposals + discussion (high prio, high complex) to Done in Longterm Aug 31, 2020
@titusdecali
Copy link

titusdecali commented Dec 28, 2020

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>

@Excalibaard
Copy link

Excalibaard commented May 12, 2022

When using dynamically added/removed routes with $router.addRoute, these don't show up in $router.options.routes. If I try to get all currently registered routes via $router.getRoutes(), it returns Array<RouteRecord> instead of Array<RouteConfig>, which doesn't contain a children key.

My solution was to loop over all routes and filter them by the parent instance I wanted to find the childRoutes for.

computed: {
  childRoutes() {

    // get parent instance from the RouteRecord.
    let routeInstance = this.$route.matched.find(
        x =>
          x.instances.default === this.$parent || x.instances.default === this // top-level at view component, or as view component
      );

    // get children
    return this.$router.getRoutes().filter(x => x.parent === routeInstance);
  }
}

This works for my case, where the component rending the child tabs is always the view component or a direct descendant of the view component, and I don't need to display multiple levels of route nesting. If your usecase requires multi-level nesting, named routes, or displaying routes that do not have an active parent instance, this will need to be tweaked a little.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request has workaround needs RFC This feature request needs to go through the RFC process to gather more information
Projects
Development

Successfully merging a pull request may close this issue.