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

Dynamically add child routes to an existing route #1156

Closed
miki2826 opened this issue Feb 11, 2017 · 43 comments · Fixed by juicyfx/juicy#677, juicyfx/juicy#681 or Netflix/dispatch#961
Closed
Labels
feature request fixed on 4.x This issue has been already fixed on the v4 but exists in v3 group[dynamic routing] Issues regarding dynamic routing support (eg add, replace, remove routes dynamically)
Projects

Comments

@miki2826
Copy link

I am building a large scale application with "mini applications" hosted within the application.
Each mini application is being developed by another team.
I would like to dynamically add nested child routes to an existing route to allow dynamic registration of each of the mini apps.

for example:

const routes = [
        {path: 'a', component: ComponentA, name: 'a'},
        {path: 'b', component: ComponentB, name: 'b'},
];

//MiniApp.js
export default Vue.extend({
    beforeCreate () {
        this.$router.addChildRoutes(this.$router.currentRoute.path, routes);
    },
    template: `
<div>
  <div class="page-host">
  <router-link to="{name: 'a'}">a</router-link>
  <router-link to="{name: 'b'}">b</router-link>
  <transition name="fade" mode="out-in">
    <keep-alive><router-view class="view"></router-view></keep-alive>
  </transition>
  </div>
</div>`,
});

//The app itself
Vue.use(Router);
import MiniApp from "./MiniApp";

const config = {
    linkActiveClass: 'active',
    scrollBehavior: () => ({x: 0, y: 0}),
    routes: [
        {path: '/mini', component: MiniApp, name: 'mini'}},
    ]
};
let router = new Router(config);
const vue = new Vue({
   router,
   template: `
<div>
  <div class="page-host">
  <router-link to="/mini">a</router-link>
  <transition name="fade" mode="out-in">
    <keep-alive><router-view class="view"></router-view></keep-alive>
  </transition>
  </div>
</div>`,
}).$mount('#app');
@fnlctrl
Copy link
Member

fnlctrl commented Feb 14, 2017

Since the MiniApp is loaded synchronously (not lazy loaded), why not simply expose its routes and use it when creating the router?
e.g.

//MiniApp.js
export const MiniAppRoutes = [
        {path: 'a', component: ComponentA, name: 'a'},
        {path: 'b', component: ComponentB, name: 'b'},
];

export default Vue.extend({...});

//////////////////////////////////////////////////////////

//The app itself
Vue.use(Router);
import MiniApp, { MiniAppRoutes } from "./MiniApp";

const config = {
    routes: [
        {path: '/mini', component: MiniApp, name: 'mini', children: MiniAppRoutes }},
    ]
};
let router = new Router(config);

@miki2826
Copy link
Author

@fnlctrl you are right when the app is loaded synchronously, but this is just an example.
The real world example will require lazy loading.

@fnlctrl
Copy link
Member

fnlctrl commented Feb 14, 2017

But the routes must be loaded synchronously, otherwise when visiting a url that uses a lazy loaded component, the router won't be able to recognize that route.

e.g.

const config = {
    routes: [
        {path: '/mini', component: () => System.import('mini-app'), name: 'mini' }},
    ]
};

Now, when directly visiting /mini/foo, the router won't be able to match it to /mini and load MiniApp, since it has no knowledge of such child routes (foo) yet.

@varHarrie
Copy link

Same case in my project.
In order to disable some routes, the child routes depend on the role of user. And after someon logined, add child routes to an existing route with lazy loading.

@fnlctrl
Copy link
Member

fnlctrl commented Feb 15, 2017

@varHarrie For that use case, we already have a better and simpler solution:
Add a check for login(auth) state inside a beforeEach hook, and redirect users elsewhere if they're not logged in, while all routes are statically loaded. Check out navigation guards docs and the auth flow example

@donnysim
Copy link

Need this also as my app loads modules, each module has a setup method that registers their properties and I need a way to add a child route dynamically.

Tried:

        this._dashboardRoutes = [
            { name: 'dashboard', path: '', component: require('../../pages/dashboard/Index.vue') },
        ];
        this._globalRoutes = [{
            path: '/',
            component: require('../../pages/DashboardWrapper.vue'),
            meta: { auth: true },
            children: this._dashboardRoutes
        }];

        this._router = new VueRouter({
            mode: 'hash',
            scrollBehavior: () => ({ y: 0 }),
            routes: this._globalRoutes,
        });

but after digging inside realized that it generates a route map only when addRoutes is called, so it does not find other routes :\

@donnysim
Copy link

donnysim commented Mar 21, 2017

Honestly, a refresh function would be all I need 🗡
Edit: nwm, it copies the route so it doesn't refresh it from the same list :\

@DanielPe05
Copy link

@posva Is there a plan to implement this? Being able to specify parent: "someRoute" when using addRoutes seems like a no brainer. In my case the application has 2 completely different layouts for authed users vs users that have not signed in. A nice approach would be to have everything that is authed under /app for example, but almost all my routes are added dynamically using addRoutes, so there is no way of specifying a parent right now.

@posva
Copy link
Member

posva commented Apr 5, 2017

Yeah, but I think that taking an optional first parameter for the parent may be better:

// parentName is a string and must match a route's name
addRoutes([parentName, ]routes)

For the moment, nobody is looking into this. We'll come when we can 🙂

@DanielPe05
Copy link

I like it @posva, I will take a stab at it as soon as I have some free time and submit a PR.

@patrickhousley
Copy link

patrickhousley commented Jul 4, 2017

Could we go one step further and add support for lazy loaded children. This could be done by allowing children to be of types: RouteConfig[] | () => RouteConfig[] | () => Promise<RouteConfig[]>?

We will also need support for deep linking into an async child route.

@posva
Copy link
Member

posva commented Jul 4, 2017

@patrickhousley It's unrelated to this issue, but you can already lazy load children components

@patrickhousley
Copy link

@posva I assume you are talking about being able to lazy load components in general. If so, I am already doing that. What I am saying is it would be nice if we could lazy load the children router configuration.

Lazy load components:

// src/routes.ts
export const routes: VueRouter.RouteConfig[] = [
  {
    path: '/lazy',
    children: [
      {
        path: '/one',
        component: async () => new Promise<Vue>(resolve => {
          require.ensure(
            [],
            async require => {
              resolve((require('./one.component.ts') as { OneComponent: Vue }).OneComponent);
            },
            'lazy'
          );
        })
      },
      {
        path: '/two',
        component: async () => new Promise<Vue>(resolve => {
          require.ensure(
            [],
            async require => {
              resolve((require('./two.component.ts') as { TwoComponent: Vue }).TwoComponent);
            },
            'lazy'
          );
        })
      }
    ]
  }
];

There is nothing wrong with this code. It is just very verbose to read and write.

Lazy loaded children configuration:

// src/routes.ts
export const routes: VueRouter.RouteConfig[] = [
  {
    path: '/lazy',
    children: async () => new Promise<Vue>(resolve => {
      require.ensure(
        [],
        async require => {
          resolve((require('./lazy/routes') as { routes: VueRouter.RouteConfig[] }).Routes);
        },
        'lazy'
      );
    })
  }
];

// /lazy/routes.ts
export const routes: VueRouter.RouteConfig[] = [
  {
    {
      path: '/one',
      component: OneComponent
    },
    {
      path: '/two',
      component: TwoComponent
    }
  }
];

@edgarnadal
Copy link

Please take a look to vue-tidyroutes it's a simple package to handle routes registration in your project.

@br11
Copy link

br11 commented Jan 12, 2018

I want the components to be able to add its own routes once they are registered in the app.
I believe that it could be done by having a
"addRoutes([parentName, ]routes)" just like @ktmswzw said.

@mike-hor
Copy link

yeah ,maybe need append some childrens.
"addRoutes([parentName, ]routes)" it's sounds great

@clementdevos
Copy link

👍

Much needed feature in our code, to handle an infinte levels hierarchy.
React Router does it : https://reacttraining.com/react-router/web/example/recursive-paths

@factoidforrest
Copy link

Nested dynamic routers would be extremely useful. The composability of vue is its strong point, and that shouldn't stop at the router. Does anyone know of an alternative Vue router that has this functionality?

@Dri4n
Copy link

Dri4n commented Feb 15, 2018

Last night I worked on this, maybe this could be a possible solution or maybe keep 2 parameters in the next PR #2064.

Greetings.
Thanks.

@factoidforrest
Copy link

@Dri4n could you explain a little bit how the new behavior from your PR works? I read it but not being very familiar with Vue internally, I'm not sure sure.

Would that allow me to add routes to a router and Vue instance that has already been initiated?

@Dri4n
Copy link

Dri4n commented Feb 16, 2018

@light24bulbs I'm sorry, but this PR is not yet accepted, basically it's about adding routes to a parent route dynamically (once this instanced vue router), this depends on the name or path of the parent route, if they accept it I will add the corresponding documentation, thank you!

@posva posva removed the 2.x label Feb 27, 2018
@posva

This comment has been minimized.

@EvanBurbidge

This comment has been minimized.

@posva posva changed the title [Feature Request] Dynamically add child routes to an existing route Dynamically add child routes to an existing route Dec 20, 2018
@jfuehner
Copy link

Any update on adding this much needed feature?

@posva posva added this to Design proposals + discussion (high prio, high complex) in Longterm Mar 26, 2019
@posva posva added the group[dynamic routing] Issues regarding dynamic routing support (eg add, replace, remove routes dynamically) label Mar 26, 2019
@givingwu
Copy link

givingwu commented May 20, 2019

I have written a simple library for dynamically add routes to an existing route, vue-mfe/src/core/router/index.js. the idea was inspired from @coxy's comment, thanks for that suggestion.

this library was not only to enhance vue-router, it be designed for Vue micro-frontends solution. so you can copy those code into your project what you need.

hope it can be helpful for someone who needs.

@Sapphire2k
Copy link

Still waiting for this feature~🌟

@DontShootMe
Copy link

DontShootMe commented Sep 9, 2019

routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route)
})

 function createRouteMap (
    routes,
    oldPathList,
    oldPathMap,
    oldNameMap
  ) {
    // the path list is used to control path matching priority
    var pathList = oldPathList || [];
    // $flow-disable-line
    var pathMap = oldPathMap || Object.create(null);
    // $flow-disable-line
    var nameMap = oldNameMap || Object.create(null);

    routes.forEach(function (route) {
  
/* ---- MODIFICATION:start  --------------------------- */
    
      let parent  = route.parent && oldNameMap[route.parent.name] ||
                    route.parent && oldPathMap[route.parent.path] ;
      
      route.parent && route.parent.name && assert(parent , 'Inexistant parent with name :\n\t' + route.parent.name);
      route.parent && route.parent.path && assert(parent , 'Inexistant parent with path :\n\t' + route.parent.path);
      
      /*addRouteRecord(pathList, pathMap, nameMap, route);*/
      addRouteRecord(pathList, pathMap, nameMap, route, parent);

/* ---- MODIFICATION:end  --------------------------- */


    });

    // ensure wildcard routes are always at the end
    for (var i = 0, l = pathList.length; i < l; i++) {
      if (pathList[i] === '*') {
        pathList.push(pathList.splice(i, 1)[0]);
        l--;
        i--;
      }
    }

. . .

By adding this modification, i think it will be possible to

Dynamically add child routes to an existing route

let initialConfig = [
  {
    name : 'app-home',
    path : '/' ,
    component : lazyLoader('/path/to/app/home.js')
  },
  { 
    name : 'app-404' ,
    path : '*' , 
    component : lazyLoader('/path/to/app/404.js') 
  } , 
];
// classic and actual way to init routes
const myRouter = new VueRouter({
  routes : initialConfig ,
})



/* Later you can do */
let modulesHolder = [
  {
    name : 'app-modules' ,
    path : '/module' ,
    component : lazyLoader('/path/to/app/modules.js')
  }
];
// classic and actual way to add routes
myRouter.addRoutes( modulesHolder ); 




/* Later again you can do */
let moduleTest = [
  {
    name : 'app-module-test' ,
    path : 'test' ,
/* - - New Key = parent : define parent by path  - - - - - - - - */
    parent : { path : '/module' } ,
    component : lazyLoader('/path/to/app/modules/test/index.js')
  }
];

// matched route = /module/test
myRouter.addRoutes( moduleTest ); 






/* Later again you can do */
let moduleOther = [
  {
    name : 'app-module-other' ,
    path : 'other' ,
/* - - New Key = parent : define parent by name  - - - - - - - - */
    parent : { name : 'app-modules' } ,
    component : lazyLoader('/path/to/app/modules/other/index.js')  
  }
];

// matched route = /module/other
myRouter.addRoutes( moduleOther );

@EvanBurbidge
Copy link

@donnysim what happens when you're on a child root and refresh the page with that?

@DontShootMe
Copy link

DontShootMe commented Sep 9, 2019

when you're on a child root and refresh the page

Mmm...
I don't really understand, if you refresh the page so the router will be refreshed too ...
But if the router is not refreshed, so you'll keep everything !
Every route is hold by the router !

Once the modification on main code done, it will open possibility ...

myRouter.addAbsentChildRoutes = ( parent, children ) {
    let list = children.filter( child =>
        let result = myRouter.match({path: child.path , name: child.name});
        let haveResult = result.matched && result.matched.length;
        if(!haveResult) return true;
        return result.matched[0].path === '*' ? true : false;
    });
    return myRouter.addChildRoutes( parent, list);
}

myRouter.addChildRoutes = ( parent, children ) {
    /* check and filter what you want */
    return myRouter.addRoutes( children.map( child => { 
        if( ! child.parent ) child.parent = parent;
        return child;
    });
}

/* and more and more . . . */

@DontShootMe
Copy link

@posva what do you think about this addition ?

routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route)
})

have to become :

    routes.forEach(function (route) {
      let parent  = route.parent && oldNameMap[route.parent.name] ||
                    route.parent && oldPathMap[route.parent.path] ;
      
      route.parent && route.parent.name && assert(parent , 'Inexistant parent with name :\n\t' + route.parent.name);
      route.parent && route.parent.path && assert(parent , 'Inexistant parent with path :\n\t' + route.parent.path);
      
      /*addRouteRecord(pathList, pathMap, nameMap, route);*/
      addRouteRecord(pathList, pathMap, nameMap, route, parent);
    });

And this little modification will permit to dynamically add child routes to an existing route !

@wesrobinson12
Copy link

Has this been implemented yet??

@StidOfficial
Copy link

Apparently no addChildRoutes, but i am interested by this feature.

Currently i use this :

var route = routes.find(r => r.path === parent_route.path)
route.children = [{
    path: route.path + "/" + child_route.alias,
    name: child_route.alias,
    component: child_route.component
}]

router.addRoutes([route])

But he generate a warn : [vue-router] Duplicate named routes definition:

Anybody can't push a commit ?

@domschmidt
Copy link

registering child routes dynamically is a feature that every router (angular/react/...) supports. the priority of this feature should be on highest level IMHO.

@JonathanDn
Copy link

@posva It seems from the comments and upvotes that this is a much needed feature. I too need it. Any news where it stands?

@posva
Copy link
Member

posva commented Feb 4, 2020

@JonathanDn This will be added once vuejs/rfcs#122 is merged. That way we can keep the same api surface for addRoute in Vue Router 3 and 4. addRoutes will likely be deprecated in favor of addRoute too

@posva posva added the fixed on 4.x This issue has been already fixed on the v4 but exists in v3 label Feb 27, 2020
@DontShootMe
Copy link

@posva
Is it possible to add this modification to the 3.x without waiting the 4.x ?
From 11 Nov 2017 to now, we were waiting for this feature, on 10 Sep 2019 was posted this code :

    routes.forEach(function (route) {
      let parent  = route.parent && oldNameMap[route.parent.name] ||
                    route.parent && oldPathMap[route.parent.path] ;
      
      route.parent && route.parent.name && assert(parent , 'Inexistant parent with name :\n\t' + route.parent.name);
      route.parent && route.parent.path && assert(parent , 'Inexistant parent with path :\n\t' + route.parent.path);
      
      /*addRouteRecord(pathList, pathMap, nameMap, route);*/
        addRouteRecord(pathList, pathMap, nameMap, route, parent);
    });

It doesn't produce any breaking changes, it just adds such long-awaited functionality.
It is based on your own source code, there is no modification of the functions, it just provides a parameter which was not originally supplied, but present in the signature of the function.

I don't know if Vue-Router 4.x will only be compatible with Vue 3.0, so for now we can have a solution for Vue 2.x and Vue-Router 3.x.

It will be available now!
So I hope you will accept this change to help us.

Thank you for your work and your time.

@titusdecali

This comment has been minimized.

@uaeprimeglobal

This comment was marked as spam.

@vuejs vuejs locked as resolved and limited conversation to collaborators Nov 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature request fixed on 4.x This issue has been already fixed on the v4 but exists in v3 group[dynamic routing] Issues regarding dynamic routing support (eg add, replace, remove routes dynamically)
Projects