简体   繁体   中英

Change a property's value in one component from within another component

I'm trying to wrap my head around hoe Vue.js works, reading lots of documents and tutorials and taking some pluralsight classes. I have a very basic website UI up and running. Here's the App.vue (which I'm using kinda as a master page).

(To make reading this easier and faster, look for this comment: This is the part you should pay attention to )...

<template>
    <div id="app">
        <div>
            <div>
                <CommandBar />
            </div>
            <div>
                <Navigation />
            </div>
        </div>
        <div id="lowerContent">

            <!-- This is the part you should pay attention to -->

            <template v-if="showLeftContent">
                <div id="leftPane">
                    <div id="leftContent">
                        <router-view name="LeftSideBar"></router-view>
                    </div>
                </div>
            </template>

            <!-- // This is the part you should pay attention to -->

            <div id="mainPane">
                <div id="mainContent">
                    <router-view name="MainContent"></router-view>
                </div>
            </div>
        </div>
    </div>
</template>

And then in the same App.vue file, here's the script portion

<script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';

    import CommandBar from './components/CommandBar.vue';
    import Navigation from './components/Navigation.vue';


    @Component({
        components: {
            CommandBar,
            Navigation,
        }
    })
    export default class App extends Vue {
        data() {
            return {
                showLeftContent: true // <--- This is the part you should pay attention to
            }
        }
    }
</script>

Ok, so the idea is, one some pages I want to show a left sidebar, but on other pages I don't. That's why that div is wrapped in <template v-if="showLeftContent"> .

Then with the named <router-view>'s I can control which components get loaded into them in the `router\index.ts\ file. The routes look like this:

    {
        path: '/home',
        name: 'Home',
        components: {
            default: Home,
            MainContent: Home,  // load the Home compliment the main content
            LeftSideBar: UserSearch  // load the UserSearch component in the left side bar area
        }
    },

So far so good! But here's the kicker. Some pages won't have a left side bar, and on those pages, I want to change showLeftContent from true to false . That's the part I can't figure out.

Let's say we have a "Notes" component that looks like this.

<template>
    <div class="notes">
        Notes
    </div>
</template>

<script lang="ts">
    import { Component, Prop, Vue } from 'vue-property-decorator';

    @Component
    export default class Notes extends Vue {
        data() {
            return {
                showLeftContent: false  // DOES NOT WORK
            }
        }
    }
</script>

Obviously, I'm not handling showLeftContent properly here. It would seem as if the properties in data are scoped only to that component, which I understand. I'm just not finding anything on how I can set a data property in the App component and then change it in a child component when that child is loaded through a router-view .

Thanks!

EDIT:

I changed the script section of the Notes component from:

<script lang="ts">
    import { Component, Prop, Vue } from 'vue-property-decorator';

    @Component
    export default class Notes extends Vue {
        data() {
            return {
                showLeftContent: false  // DOES NOT WORK
            }
        }
    }
</script>

to:

<script lang="ts">
    import { Component, Prop, Vue } from 'vue-property-decorator';

    @Component
    export default class Notes extends Vue {
        mounted() {
            this.$root.$data.showLeftContent = false;
        }
    }
</script>

And while that didn't cause any compile or runtime errors, it also didn't have the desired effect. On Notes , the left side bar still shows.

EDIT 2:

If I put an alert in the script section of the Notes component:

export default class Notes extends Vue {
    mounted() {
        alert(this.$root.$data.showLeftContent);
        //this.$root.$data.showLeftContent = false;
    }
}

The alert does not pop until I click on "Notes" in the navigation. But, the value is "undefined".

EDIT 3:

Struggling with the syntax here (keep in mind this is TypeScript, which I don't know very well!!)

在此处输入图像描述

Edit 4:

Inching along!

export default class App extends Vue {
    data() {
        return {
            showLeftContent: true
        }
    }

    leftContent(value: boolean) {
        alert('clicked');
        this.$root.$emit('left-content', value);
    }
}

This does not result in any errors, but it also doesn't work. The event never gets fired. I'm going to try putting it in the Navigation component and see if that works.

As it says on @lukebearden answer you can use the emit event to pass true/false to the main App component on router-link click.

Assuming your Navigation component looks like below, you can do something like that:

#Navigation.vue
<template>
  <div>
      <router-link to="/home" @click.native="leftContent(true)">Home</router-link> - 
      <router-link to="/notes" @click.native="leftContent(false)">Notes</router-link>
  </div>
</template>

<script>
export default {
  methods: {
    leftContent(value) {
      this.$emit('left-content', value)
    }
  }
}
</script>

And in your main App you listen the emit on Navigation :

<template>
  <div id="app">
    <div>
      <Navigation @left-content="leftContent" />
    </div>
    <div id="lowerContent">
      <template v-if="showLeftContent">
         //...
      </template>

      <div id="mainPane">
         //...
      </div>
    </div>
  </div>
</template>

<script>
  //...
  data() {
    return {
      showLeftContent: true
    }
  },
  methods: {
    leftContent(value) {
      this.showLeftContent = value
    }
  }
};
</script>

A basic approach in a parent-child component relationship is to emit events from the child and then listen and handle that event in the parent component.

However, I'm not sure that approach works when working with the router-view. This person solved it by watching the $route attribute for changes. https://forum.vuejs.org/t/emitting-events-from-vue-router/10136/6

You might also want to look into creating a simple event bus using a vue instance, or using vuex.

If you'd like to access the data property (or props, options etc) of the root instance , you can use this.$root.$data . (Check Vue Guide: Handling Edge )

For your codes , you can change this.$root.$data.showLeftContent to true/false in the hook= mounted of other Components, then when Vue creates instances for those components, it will show/hide the left side panel relevantly.

Below is one demo :

 Vue.config.productionTip = false Vue.component('child', { template: `<div :style="{'background-color':color}" style="padding: 10px"> Reach to root: <button @click="changeRootData()">Click me!</button> <hr> <slot></slot> </div>`, props: ['color'], methods: { changeRootData() { this.$root.$data.testValue += ' :) ' } } }) new Vue({ el: '#app', data() { return { testValue: 'Puss In Boots' } } })
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script> <div id="app"> <h2>{{testValue}}</h2> <child color="red"><child color="gray"><child color="green"></child></child></child> </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM