vue.jsvue-props

How to Pass value to Non Nested components in Vue 3 composition Api


I have a MainLayout , SideBar Component and a TopBar Component. I placed a SideBarButton Component on the the TopBar Component, which when clicked should toggle the sidebar.

How do i pass the ref variable of SideBarButton to the SideBar Component?

MainLayout.vue
<template>
        <el-header><TopBar /> </el-header>
        <el-container>
          <el-aside ><SideBar /> </el-aside>
        <el-container>
</template>
TopBar.vue
<template>
  <el-menu-item>
        <SideBarButton />
  </el-menu-item>
</template>
SideBarButton.vue
<template>
    <el-button @click="toggleCollapsed"/>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const isCollapse = ref(false)

const toggleCollapsed = (e) => {

      isCollapse.value = !isCollapse.value;

    };
</script>
SideBar.vue
<template>
<el-menu
    default-active="2"
    class="el-menu-vertical-demo"
    :collapse="isCollapse"
    @open="handleOpen"
    @close="handleClose"
  >
</template>
<script setup lang="ts">

</script>

How do i get the value of isCollapse in this SideBar Component? I have tried for inject/provide but it didnt work. Also the SideBarButton Component and SideBarComponent are not parent and child components, so Props do not work.


Solution

  • Method 1: Using props and emits

    Emit the value from SideBarButton to TopBar, re-emit to MainLayout, then use the value to set a prop on SideBar

    SideBarButton.vue

    <script setup lang="ts">
    const emit = defineEmits(['toggleCollapsed'])
    const toggleCollapsed = (e) => {
      isCollapse.value = !isCollapse.value;
      emit('toggleCollapsed', isCollapse.value)
    };
    </script>
    

    TopBar.vue

    <template>
      <el-menu-item>
            <SideBarButton @toggleCollapsed="$emit('toggleCollapsed', $event)" />
      </el-menu-item>
    </template>
    

    MainLayout.vue

    <template>
      <el-header>
        <TopBar @toggleCollapsed="isCollapse = $event" />
      </el-header>
      <el-container>
        <el-aside>
          <SideBar :isCollapse="isCollapse" />
        </el-aside>
      <el-container>
    </template>
    
    <script setup>
    const isCollapse = ref(false)
    </script>
    

    Sidebar.vue

    <script setup lang="ts">
    defineProps({
      isCollapse: {
        default: false
      }
    })
    </script>
    

    Vue Playground demo

    Method 2: Using v-model and defineModel()

    New in Vue 3.4, the defineModel macro can now be used with v-model. It's basically still doing the same job as setting up props and emits under the hood, but less code.

    MainLayout.vue

    <template>
      <el-header>
        <TopBar v-model="isCollapse" />
      </el-header>
      <el-container>
        <el-aside>
          <SideBar :isCollapse="isCollapse" />
        </el-aside>
      <el-container>
    </template>
    
    <script setup>
    const isCollapse = ref(false)
    </script>
    

    TopBar.vue

    <template>
      <el-menu-item>
        <SideBarButton v-model="isCollapse" />
      </el-menu-item>
    </template>
    
    <script setup>
      const isCollapse = defineModel()
    </script>
    

    SideBarButton.vue

    <template>
      <el-button @click="value = !value"/>
    </template>
    
    <script setup lang="ts">
    const value = defineModel()
    </script>
    

    SideBar.vue

    (same as Method 1)

    <script setup lang="ts">
    defineProps({
      isCollapse: {
        default: false
      }
    })
    </script>
    

    Vue Playground demo

    It's not clear in your question whether SideBar.vue also needs to be able to set the value of isCollapse, but my code in both methods assumes it does not. If it does, you can use the same strategies again with emitting the change back up to MainLayout or using v-model/defineModel again with SideBar.