6
votes

In my SPA app, I have an <app-view> wrapper which handles base app code (load user data, render navbar and footer, etc) and has a slot for rendering the actual page. This slot is rendered only if the user data is available.

This wrapper was created because some pages needed a different base code, therefore I couldn't keep this base code in the main app containing <router-view> anymore.

I tried looking if vue-router provides advanced options or suggests a design pattern for switching base code, didn't find anything.

The problem is that the child component will be rendered before the parent component is mounted, i.e. before the parent decides not to render the child component (because it's loading user data). This causes errors like undefined as no attribute foo.

Because of that, I'm looking for a way to defer child rendering until its parent is mounted.

4
have you tried v-cloak ?birdspider
No, but isn't it just for hiding the html templates before vue is ready? I'm using vue templates, not html.megapctr
I haven't tried putting v-if in a <slot> tag. I wonder...?Roy J

4 Answers

17
votes

I had a similar problem though not with a SPA. I had child components that needed data from the parent. The problem is that the data would only be generated after the parent has finished mounting so I ended up with null values in the children.

This is how I solved it. I used v-if directive to mount the children only after the parent has finished mounting. (in the mounted() method) see the example below

<template>
  <child-component v-if="isMounted"></child-component>
</template>
<script>
  data() {
     isMounted: false
  }, mounted() {
     this.isMounted = true
  }
</script>

after that, the child could get the data from the parent. It is slightly unrelated but I hope it gives you an idea. Think before you downvote.

8
votes

After trying a few options, it looks like I need to bite the bullet and explicitly define the data that my components depend on, like so:

<app-view>
  <div v-if='currentProfile'>
    ...
  </div>
</div>

(currentProfile is received from vuex store getter, and is fetched within app-view)

1
votes

For any of you that wants to show the child component as soon as the parent components gets data from an API call then you should use something like this:

<template>
  <child-component v-if="itemsLoaded"></child-component>
</template>

<script>
  data() {
     itemsLoaded: false
  },
  methods: {
      getData() {
          this.$axios
              .get('/path/to/endpoint')
              .then((data) => {
                  // do whatever you need to do with received data
                  
                  // change the bool value here
                  this.itemsLoaded = true
          })
          .catch((err) => {
              console.log(err)
          })
      },
  }, 
  mounted() {
     this.getData()

     // DONT change the bool value here; papa no kiss
     this.itemsLoaded = true
  }
</script>

If you try to change the boolean value this.itemsLoaded = true in the mounted() method, after calling the getData() method, you will get inconsistent results, since you may or may not receive the data before the this.itemsLoaded = true is executed.

0
votes

You can actually put the v-if on the <slot> tag in your component.

new Vue({
  el: '#app',
  render: function(createElement) {
    return createElement(
      // Your application spec here
      {
        template: `<slotty :show="showSlot"><span> here</span></slotty>`,
        data() {
          return {
            showSlot: false
          }
        },
        components: {
          slotty: {
            template: `<div>Hiding slot<slot v-if="show"></slot>.</div>`,
            props: ['show']
          }
        },
        mounted() {
          setTimeout(() => this.showSlot = true, 1500);
        }
      }
    );
  }
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
</div>