Home » vue » Active class on nested routes

Active class on nested routes

Posted by: admin November 26, 2021 Leave a comment

Questions:

I got this problem:

Having these routes:

user => /user
user.edit => /user/edit/:id(\d+)?
user.view => /user/:id(\d+)?

user.role => /user/role
user.role.edit => /user/role/edit/:id(\d+)?
user.role.view => /user/role/:id(\d+)?

I would like to create a sidebar nested menu.

<NavbarDropdown :title="'Users'" :name="'user'">
    <template #dropdown>
        <router-link
                v-slot="{ href, route, navigate, isActive }"
                :to="{name: 'user'}">
            <a :class="{'bg-gray-200 text-gray-900': isActive}"
               :href="href"
               @click="navigate">
                Users
            </a>
        </router-link>
        <router-link
                v-slot="{ href, route, navigate, isActive }"
                :to="{name: 'user.role'}">
            <a :class="{'bg-gray-200 text-gray-900': isActive}"
               :href="href"
               @click="navigate">
                Roles
            </a>
        </router-link>
    </template>
</NavbarDropdown>

This is the Dropdown-Component:

<template>
    <div>
        <router-link
                v-slot="{ href, isActive }"
                :to="{name: name}">
            <div>
                <a :class="{'bg-gray-200 text-gray-900': isActive}"
                    :href="href"
                    @click.prevent="open = !open">
                    {{ title }}
                </a>
                <!-- Expandable link section, show/hide based on state. -->
                <div v-if="open || isActive" class="mt-1 space-y-1">
                    <slot name="dropdown"></slot>
                </div>
            </div>
        </router-link>
    </div>
</template>
<script>
    export default {
        name: 'NavbarDropdown',
        props: {
            title: {
                type: String,
            },
            name: {
                type: String,
                default: ''
            },
        },
        data: () => ({
            open: false,
        }),
        watch: {
            '$route' () {
                this.open = this.$route.matched.some (({name}) => name === this.name);
            }
        },
    };
</script>

Now my problem is:

The item

        <router-link
                v-slot="{ href, route, navigate, isActive }"
                :to="{name: 'user'}">
            <a :class="{'bg-gray-200 text-gray-900': isActive}"
               :href="href"
               @click="navigate">
                Users
            </a>
        </router-link>

is active, even if the "Roles" route is active. If I want to use isExactActive, the .edit/.view-routes are not triggering the "active" state.

Any suggestions here?

For the moment I created a method to check the active state:

isExtendedActive(route){
    return this.$route.matched.some (({name}) => name === `${route}` || name === `${route}.view` || name === `${route}.edit`)
}
Answers:

I have found leveraging the route meta tags to be useful in helping to make navigation modules and keeping things highlighted based on what items they fall under. Everything else I tried that was baked into the route seemed hackish.

Something like:

{
  path: /user/edit/:id?,
  name: userEdit,
  componet: //componet import here,
  meta: {
   module: 'user'
  }
},
{
  path: /user/role/:id?,
  name: userEdit,
  componet: //componet import here,
  meta: {
   module: 'user',
   subModule: 'userRole'
  }
}

You can then access the meta data in your template with $route.meta.module ect, using those values to decide what styles to apply.