5
votes

I'm using the vue-router and have a question regarding subRoutes. I would like to set up my routes so the main routes are listings and the subRoutes are things like edit/add/etc.

I want the subRoute components to replace the <router-view> of the parent route. The way I understand the documentation and from what I've tested, it looks like I should define another <router-view> in the parent components template for the subRoute to render into but then the user-list would remain visible.

Example routes:

'/users': {
    name: 'user-list',
    component(resolve) {
        require(['./components/users.vue'], resolve)
    },
    subRoutes: {
        '/add': {
            name: 'add-user',
            component(resolve) {
                require(['./components/users_add.vue'], resolve)
            }
        }
    }
}

Main router view:

<!-- main router view -->
<div id="app">
    <router-view></router-view>
</div>

User list:

<template>
    <a v-link="{ name: 'add-user' }">Add</a>
    <ul>
        <li>{{ name }}</li>
    </ul>
</template>

Add user:

<template>
    <div>
        <a v-link="{ name: 'user-list' }">back</a>
        <input type="text" v-model="name">
    </div>
</template>

When I click on "Add", I want to be filled with the add-user template. Is this possible?

Also, can I establish a parent-child relationship between the user-list and add-user components? I would like to be able to pass props (list of users) to the add component and dispatch events back up to the user-list.

Thanks!

2
When you go to /user the user-list will be rendered in your main view. Automatically when you go /user/add il will be rendered in the same <router-view>. That's the default behaviour. Yes you can have parent-child communication by using props etc.highFlyingSmurfs
It doesn't seem to be the default behaviour for me. When I define subRoutes and navigate to them, the root route stays active. When I define the subRoutes as root-level routes, it works :/Christof
That's not very helpful. I read the documentation forwards and backwards. In the nested chapter it states "a rendered component can also contain its own, nested <router-view>" but here the last example uses /c as a container for <router-view>. And that's the behaviour I'm seeing as well.Christof

2 Answers

7
votes

It sounds like those edit/add routes should not be subRoutes, simple as that.

just because the path makes it seem like nesting doesn't mean you have to actually nest them.

'/users': {
    name: 'user-list',
    component(resolve) {
      require(['./components/users.vue'], resolve)
    }
},
'users/add': {
    name: 'add-user',
    component(resolve) {
      require(['./components/users_add.vue'], resolve)
    }
}
2
votes

So I played around with this for quite a bit and finally figured out how this can be achieved. The trick is to make the listing a subRoute too and have the root-level route just offer a <router-view> container for all child components to render into. Then, move the "list loading" stuff to the parent component and pass the list to the list-component as a prop. You can then tell the parent to reload the list via events.

That's it for now, it works like a charm. Only drawback is that I have another component that I didn't really need. I'll follow up with an example when I find the time.

edit

Added an example as requested. Please note: this code is from 2016 - it may not work with current Vue versions. They changed the event system so parent/child communication works differently. It is now considered bad practice to communicate in both directions (at least the way I'm doing it here). Also, these days I would solve this differently and give each route it's own module in the store.

That said, here's the example I would have added back in 2016: Let's stick with the users example - we have a page with which you can list/add/edit users.

Here's the route definition:

'/users': {
    name: 'users',
    component(resolve) {
        // this is what I meant with "root-level" route, it acts as a parent to the sub routes
        require(['./pages/users.vue'], resolve)
    },
    subRoutes: {
        '/': {
            name: 'user-list',
            component(resolve) {
                require(['./pages/users/list.vue'], resolve)
            }
        },
        '/add': {
            name: 'user-add',
            component(resolve) {
                require(['./pages/users/add.vue'], resolve)
            }
        },
        // etc.
    }
},

users.vue

<template>
  <div>
    <router-view :users="users"></router-view>
  </div>
</template>


<script>
/**
 * This component acts as a parent and sort of state-storage for
 * all user child components. It's the route that's loaded at /users.
 * the list component is the actual component that is shown though, but since
 * that's a sibling of the other child components, we need this.
 */
export default {
  events: {
    /**
    * Update users when child says so
    */
    updateUsers() {
      this.loadUsers();
    }
  },

  data() {
    return {
      users: []
    }
  },

  route: {
    data() {
      // load users from server
      if (!this.users.length) {
        this.loadUsers();
      }
    }
  },

  methods: {
    loadUsers() {
      // load the users from an API or something
      this.users = response.data;
    },
  }
}
</script>

./pages/users/list.vue

<template>
  <ul>
    <li v-for="user in users" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

<script>
export default {
  props: ['users'],
  // the rest of the component
}
</script>