6
votes

I'm learning Vue atm, and I'm having trouble getting props passed between children and parent components through Vue Routes. I've got a Layout component, that has a wrapper DIV and looks like this:

<template>
    <div class="container" v-bind:class="cssClass">
      <router-view></router-view>
    </div>
</template>

<script>
export default {
  name: 'Layout',
  props: ['cssClass']
}
</script>

and I've got my routes defined in my base App JS, and looks like the below. So my view on first load has the class "container-animated" and all is good with the world.

const router = new VueRouter({
    routes: [
      { path: '/', component: Layout, props: { cssClass: 'container-animated' },
        children: [
          { path: '', component: Homepage },
          { path: '/hello-world', component: HelloWorldPage, props: { cssClass: '' } }
        ]
     },
    ]
});

However, once I hit the /hello-world route, I want to pass an empty cssClass props to down to Layout, (which HelloWorldPage is currently nested inside) - how would I go about that? Are props even the mechanism to achieve that?

3
Why do you want to pass down an empty prop? Should this prop be changed in the future? What is the child supposed to do with the cssClass? Should the parent notice, which component is active and change cssClass of the respective child component?Bennett Dams
Well in this instance I want to remove the css modifer in the parent.. in another route I may want to add a different modifier depending on the route to something else. Yes to your last statement on the parent being aware of the child component prop.Squiggs.

3 Answers

4
votes

I figured it out, whether this is the optimum solution for my problem is anyone's guess.

It appears child props aren't picked up automatically by the parent when passed on the Vue Router. So once the components are built / injected dynamically, they each call my custom childinit event, which emits back to the router view defined in the parent (Layout). I set a local variable in the parent to the value of the emitted child, and then bind the class to it.

const router = new VueRouter({
    routes: [
      {
        path: '/',
        component: Layout,
        children: [
          {
            path: '',
            component: Homepage,
            props: { cssClass: 'home' },
          },
          {
              path: '/helloworld',
              component: HelloWorldPage,
              props: { cssClass: 'helloworld' }
          }
        ]
      }
    ]
});

My layout component:

<template>
    <div class="container" v-bind:class="className">
      <router-view v-on:childinit="onChildInit"></router-view>
    </div>
</template>

<script>
export default {
  name: 'Layout',
  props: ['cssClass'],
  data() {
    return {
      className : ''
    }
  },
  methods: {
    onChildInit( value ){
      this.className = value;
    }
  }
}
</script>

My Homepage component:

export default {
  name: 'Homepage',
  props: ['cssClass'],
  created() {
    this.$emit('childinit', this.cssClass);
  }
}

The HelloWorld component emits as well, it's possible that the created method doesn't need to be replicated; might have a go at seeing if you can extend a base component that will always emit on init for both components.

0
votes

I know that this is old, but you don't have to worry about creating a method in component which emits your value. this is my solution.

Your layout:

<template>
<div class="container" v-bind:class="className">
    <router-view></router-view>
</div>
</template>
<script>
export default {
  name: 'Layout',
  props: ['cssClass'],
  data() {
    return {
      className : ''
    }
  },
  // set class initially
  created () {
    this.setClassName(this.$route)
  },
  // and when route changes
  watch: {
    $route: function (val) {
        this.setClassName(val)
    }
  },
  methods: {
    setClassName( Route ){
      // each matched route is checked for cssClass from top to bottom, so you can override
      Route.matched.forEach((route) => {
        if (route.props.default && route.props.default instanceof Object && 'cssClass' in route.props.default) {
          this.className = route.props.default.cssClass
        }
      })
    }

  }
}
</script>

This way, everything stays on the Layout component. It's not ideal solution either. I can imagine using router.afterEach() and set the value to Vuex store.

-1
votes

Let me explain you how vue works:

You got your parent component. Layout.vue

<template>
  <div id="app" class="container-fluid">
    <router-view/>
  </div>
</template>
<style>
 .container-fluid {
background-color:blue; //as defined in the parent, everything inside #app will inherit this class
}
</style>

Now, your vue router will have to look like this:

{
    path: '/',
    name: 'Layout',
    component: Layout,
    children: [
        { path: '', component: Create, name: 'Create' },
    ]
}

Since you have defined that inside the Layout.vue will inherit everything inside .container-fluid, the component Create will inherit the classes defined in its parent (Layout)

Hope this works.

Regards,