vuejs / vue-router

🚦 The official router for Vue 2
http://v3.router.vuejs.org/
MIT License
18.99k stars 5.06k forks source link

Allow components to define their routes in their definition file instead of having them all in one place #298

Closed asynkayo closed 3 years ago

asynkayo commented 8 years ago

Since components are already taking care of not polluting other places, I believe it would be really nice to have components declare their own routes (subroutes actually) in their own space. This would really help for huge apps and it would favor components reusability. I was thinking of a 'routes' object in the component's definition object but this could be in a vue file as well.

b12k commented 8 years ago

Very arguable feature. I personally do no like this approach. It kind of Symfony and some .Net MVC workarounds... I like to open a single file with routes and see how app works.

asynkayo commented 8 years ago

Well of course, just like any features they are arguable. I guess it's not about your personal taste though but potential flexibility at zero cost. Components are exactly that. I'd like to open only one file to see how a component work. Adding dev tools to dump all the routes of an app should also be quite easy...

jonagoldman commented 8 years ago

It can be useful if you have big app with totally isolated parent components. For example if you have a Public component (not authenticated users) and a Private component (authenticated users), then maybe you can split the routes in two. But in general it's better to have a file where you can see all the possible routes.

amirrustam commented 8 years ago

I can see how this would be useful for larger apps. Coming from the Python world, the popular Django framework does something similar. There is a root route map, and you can include route maps to individual root routes.

So here is a rough Django inspired proposal:

route.map({
  '/foo' : {
    component: Foo
  }
})

And the Foo component can provide the nested routes with in a map property of the already implemented route property:

var Foo = Vue.extend({
  // ...
  route: {
    map: {
      'bar/:id' { component: Bar }
    }
 }
})

The route /foo/bar/:id would route to the Bar component.

I thought this up quickly, so I'm not sure what consequences might come out of this method of defining routes.

Jnesselr commented 8 years ago

I do like the idea of being able to separate them like this, but the v-link would have to be the full path and not the partial path, in case I needed to link to something outside of it. This would, in my mind, make a sub-component possibly depend on another component.

Other than that issue, I do like the idea, because it lets me get a high level overview of the routes without having to dive in to any particular one. When you have a ton of components and you're dealing with routes and subroutes, it gets a bit tedious.

perkola commented 8 years ago

While it is arguable to keep all routes and sub-routes together to improve readability, is there anyway to get around having to import all the components used in that routes file? For example:

import Archive from './Archive.vue'
import ArchiveIndex from './components/archive/Index.vue'
import ArchiveShow from './components/archive/Show.vue'

...

router.map({
    '/archive': {
        component: Archive,
        subRoutes: {
            '/': { component: ArchiveIndex },
            '/:slug': { component: ArchiveShow }
        }
    }
})

Somehow it feels like the components of the sub-routes should be imported in the parent component.

chmln commented 8 years ago

is there anyway to get around having to import all the components used in that routes file?

If you use webpack you can import components into router.map() like this `

'/register': {
    name: "register",
    component: resolve => require(['./Register.vue'], resolve)

},

'/login': {
    name: "login",
    component: resolve => require(['./Login.vue'], resolve)
},`
perkola commented 8 years ago

@chmln does Browserify support this? I can't seem to find it in the documentation.

javisperez commented 8 years ago

@perkola yes, browserify support it, i'm using it with gulp, here's an example of how i'm doing it:

router.map({
    '/manage': {
        component: require('./manage/manage.vue')
    }
});
airtonix commented 8 years ago

modules providing their own routes is how Django works too.

Promotes modularising complete functionality.

@perkola, the browserify example doesn't actually do the same thing as the webpack example. read more here: http://router.vuejs.org/en/lazy.html

lkiarest commented 8 years ago

It's helpful when we need to add subRoutes at runtime, when our webapp supports widgets that are developed by 3rd parties and dynamically loaded.

inssitu commented 8 years ago

I really like the idea... See this in a component would be awasome

route: {
  name: 'newPosts',
  path: 'posts/news'
}
jasonbodily commented 8 years ago

There is a lot of component-specific logic for routes that goes in the activate or beforeActivate of the route. So if you have one routes file, you'll end up having a lot of component specific logic there. This makes me cringe. Am I right in reading it this way?

EDIT: I didn't realize the components hold the 'beforeRouteEnter' or resolve functionality. New paradigm to me, still trying to figure out how I feel about it. But that changes my comment~

nkostadinov commented 8 years ago

We should be just able to modify the routes at runtime and everyone will handle the routing as he pleases.

rickhall commented 7 years ago

I'm new to Vue and Vue Router, but my first thought when starting to play around with nested routes was, "Wow, this is really unmodular and won't scale very well." The fact that my component doesn't contain its own effective "template" and I instead have to look into and/or modified some global configuration to get what I want seems really odd to me.

+1 for adding nested routes directly into components.

simplesmiler commented 7 years ago

@rickhall I was thinking this way too. But after getting mileage with router I changed my mind. Routes being disconnected from the "controllers" (the route-handling components) is what makes the setup modular.

  1. It reflects the fact that the address bar is a unique resource.
  2. It makes your controllers not rely on presence of each other.
  3. It makes your controllers reusable in different contexts (with different host and content).
  4. It still allows you to reuse "route subtrees" at will.
rickhall commented 7 years ago

I think there are potentially two different ways to view sub-routes: 1) treat them as internal implementation details of the component or 2) treat them as API of the component.

I was coming at it from (1), but I can certainly see that (2) makes sense too. But only having option (2) as an approach effectively forces me into exposing stuff as API, even when that might not be my intent.

The mere fact that I can bind data and/or register for events in nested router-view elements indicates that the component is expecting something specific to happen and the contents of the router-view elements cannot be freely substituted without impacting the component behavior.

taotao365s commented 7 years ago

yeah, I need too.

just open createMatcher method, right? or you can create new method which called refreshRoute in VueRouter

:+1:

nkostadinov commented 7 years ago

It will be really nice if components could define their own routes/subroutes. It will make the routing much more flexible and consistent because only registered components will "add" routes to the router.

coding2012 commented 7 years ago

While there may be some odd work-arounds, but I really would love to be able to do something like this:

main.ts

new Vue({
  el: '#app-main',
  router: new VueRouter({
    routes: [
      { path: '/', component: HomeComponent },
      { path: '/products', component: ProductsComponent },
      { path: '/categories', component: CategoriesComponent },
    ]
  }),
  components: {
    'navbar': NavbarComponent
  }
});

categories.ts

@Component({
  template: `<div>
  Categories
  <router-view></router-view>
</div>
`,
  router: new VueRouter({
    routes: [
      { path: '', component: ProductsComponent },
      { path: 'somethingElse', component: NavbarComponent },
    ]
  }),
  components: {
    'products': ProductsComponent
  }
})
export class CategoriesComponent extends Vue {

This is an incomplete, non-working example, obviously, but just to illustrate.

aaroncmoore commented 7 years ago

I would like to advocate for this feature. I am new to Vue and Vue-Router and so far I've enjoyed my experience with both. However, after re-structuring my growing app and trying to break up the quickly growing router file, I found this limitation frustrating.

For myself, an App's structure is reflected in it's folder and file layout. I expect this from any project I must maintain. For example, to find the Account Preferences page and components, I do not look in the router file to find it's path and behavior, I look in the Pages/AccountPreferences folder and open AccountPreferences.Vue.

That being said, I can appreciate a single file approach for smaller apps. I would just like to see Vue-Router offering an alternative option for people like me that would like components to take ownership of their route definitions.

joelmandell commented 7 years ago

@aaroncmoore I was looking for this...So I found in the documentation router.addRoutes(). So I 'inject' router in main.js to the const app = new Vue({}). And then in my components i use this.$router.addRoutes(). I works for me

joelmandell commented 7 years ago

Ooh...I just now found out that it doesnt replace existing routes with the same path names. It's something I need. Turns out I have to wait for this one #1129 :)

mitar commented 7 years ago

For Meteor, @Akryum made this great simple addition, router factory, which allows one to register routes lazily, and then at the end all routes are defined at once.

You can define then route inside a component like:

<template>
  <div>My component.</div>
</template>

<script>
  import Vue from 'vue';
  import {RouterFactory} from 'meteor/akryum:vue-router2';

  const component = {
    // ... component code
  };

  RouterFactory.configure((factory) => {
    factory.addRoutes([
      {
        path: '/',
        name: 'home',
        component,
      },
    ]);
  });

  export default component;
</script>

You just define a set of callbacks which are called then all together.

nkostadinov commented 7 years ago

Adding a <routing> tag in Vue components also will be very handy for a component to define its own routes(subroutes)

rjwittams commented 7 years ago

I think this issue is fixed by https://github.com/vuejs/vue-router/commit/759df369733414306099099f63518cdb02a0e117 ?

mrjones2014 commented 7 years ago

any chance of this being implemented? I have a large Vue project and having to manually add all the routes to a single file is getting hard to manage; it's also not the obvious place to look for new team members. I would LOVE to see this feature implemented.

javisperez commented 7 years ago

@mrjones2014 I dont know if tit would help, but what I do for mid or large projects is that I have a singleton class with a route and child methods, which writes the routes into an object and then just pass that object to VueRouter, works great and that way each component can have its own route :)

I'll try to make a gist later for it and place it here, might help some guys.

mrjones2014 commented 7 years ago

@javisperez do you still have to maintain a routes list globally somewhere, or can it somehow be called from components themselves on register? I'd definitely like to take a look at that gist. Ideally, I'm looking for a solution that allows me to somehow specify a component as a "top-level" component (i.e. one that represents a complete UI screen on the frontend), and if specified as such, require that it also specify a route property (which ideally could be either a string or an array of strings).

edgarnadal commented 7 years ago

@mrjones2014 we (@javisperez and I) have released the package vue-tidyroutes with our approach to solving this issue.

Please take a look on it: https://github.com/edgarnadal/vue-tidyroutes

Feedback is highly appreciated.

mrjones2014 commented 7 years ago

@javisperez @edgarnadal is there a way to use this from within component files in the single file component (*.vue file) format?

javisperez commented 7 years ago

@mrjones2014 yes, check this basic example: https://github.com/edgarnadal/vue-tidyroutes/blob/master/example/component1.js that can be used on a .vue file, like this;

component1.vue


<script>
import VueTidyRoutes from 'vue-tidyroutes';

const Component1 = { name: 'component1',

data() {
    return {
        foo: 'bar'
    };
}

};

// Your routes definition here VueTidyRoutes.route('/component1', { name: 'component1', component: Component1 });

export default Component1;

mrjones2014 commented 7 years ago

@javisperez heh, duh. The routes don't seem to be working if I import the components through an index.js file; do I have to import the components each individually, directly in my main.js file?

javisperez commented 7 years ago

@mrjones2014 🤔 it should be working just fine on an index.js, we are actually using it on some large projects with no problem. Please feel free to open an issue (https://github.com/edgarnadal/vue-tidyroutes/issues) with some reproduction steps or links, so we can have a look; doing it here would be off topic :)

mrjones2014 commented 7 years ago

@javisperez LOL nevermind; I forgot to set mode: 'history' and was using real URLs

MeirionHughes commented 6 years ago

@rjwittams comment seems to have gone unnoticed - unless I'm mistaken, from the looks of it 759df36 solves the nested router problem (landed in 3.0.1) at the component level.

[edit] doesn't look like it solves navigating to a route (from the root level) into the nested routers.

JoeyHuang1 commented 6 years ago

The original feature request should help a lot for component encapsulation in large project. Without it, need to know how the sub-component internal (the route info) and put it in the top level, which is harder to reuse a component. Would like to see such feature in new Vue version, or integrate the TidyRoutes implementation into Vue.

RickMeijer commented 6 years ago

I'm a bit insecure posting this, since I'm just starting out with Vue, and I'm not terribly bright to begin with. It seems too obvious, making me feel like I'm missing something (either in the discussion or in my implementation) but my current solution is this:

// components/component.vue
import SomeChildComponent from './';

export default {
  routes: [
    { path: "/somechildpath", component: SomeChildComponent },
  ]
};

And in my main routes file

// router.js
import Vue from "vue";
import Router from "vue-router";
import Component from "components/component.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  routes: [
    {
      path: "somepath",
      component: Component,
      children: Component.routes
    }
  ]
});

It appears this can be nested as deep as you want/need. So basically my toplevel component exposes child routes that are being set in that component. I'm a big fan of how angular does this esp. combined with lazy loading.

[edit]Of course it's not necessary to add a routes property to your default export; there are several ways to export that; but principle seems to remain the same, no?[/edit]

kferrone commented 5 years ago

I actually have already done this before. I have a project which includes the route info inside the component. However it is not using .Vue files. Here is a good example: https://github.com/kferrone/kferrone.github.io/tree/master/collections/_views/contact-me

posva commented 3 years ago

Given the current state of this issue, there hasn't been any interest in implementing it in core but it seems some people like it while others don't. Therefore, it would be a nice addition as a plugin

If anybody still thinks this is worth including in core, open a discussion or an RFC in the vuejs/rfcs repository to continue the discussion!