VanCoon
VanCoon

Reputation: 431

Unable to display data from vue instance in a component that is inside another componnt

I have a Vue instance that has data that I am trying to access from a component that is inside another component to iterate through all values with v-for from the items inside data, in this case either banners or posts.

I have reviewed the composing components section on the vue.js website, using their posts data as an example but am unable to utilize the posts data inside my blog-post component.

I have tried adding props to the blog-post component, to the nav-home component and have tried modifying the data in my vue app instance to return all values of data and tried to add the following to the mounted property, this.$emit('posts', this.posts) without success.

Console.log(this.posts) of course returns the data, however it isn't accessible in the nested components either product-banner or blog-post when included in the nav-home component.

                    Vue.component('nav-home', {
                         props: ['posts','banners'],
                        template: `<div>
                        <h1> Home component </h1>
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

                        <!-- banner -->
                        <product-banner
                            v-for="banner in banners"
                            v-bind:key="banner.id"
                            v-bind:banner="banner">
                        </product-banner>
                    
                            <!-- post -->
                        <blog-post
                            v-for="post in posts"
                            v-bind:key="post.id"
                            v-bind:post="post">
                        </blog-post>
                    
                        </div>`
                    })

                    Vue.component('blog-post', {
                        props: ['posts'],
                        template: `<h3>{{posts.title}}</h3>`
                    })

                    Vue.component('product-banner', {
                        props: ['banner'],
                        template: `<div id="product-banner">
                                        <div class="product-container">
                                            <h2>{{banner.title}}</h2>
                                            <p>{{banner.content}}</p>
                                        </div>
                                    </div>`
                    })

                    Vue.directive('focus', {
                        inserted: function(el) {
                            el.focus();
                        }
                    })

                    new Vue({
                        el: '#app',
                        data: {
                            currentNav: 'Home',
                            navs: ['Home'],
                            posts: [
                                { id: 1, title: 'Title one' },
                                { id: 2, title: 'Title two' },
                                { id: 3, title: 'Title three' }
                            ],
                            banners: [
                                {id: 1, title: 'Title 1', content: 'Content 1'},
                                {id: 2, title: 'Title 2', content: 'Content 2'},
                                {id: 3, title: 'Title 3', content: 'Content 3'}
                            ]
                        },
                        computed: {
                            contentComponent: function() {
                                return 'nav-' + this.currentNav.toLowerCase()
                            }
                        },
                        directive: {
                            focus: {
                                inserted: function(el) {
                                    el.focus()
                                }
                            }
                        }

                    })
@charset "utf-8";

body {
  font-family: tahoma;
  color:#282828;
  margin: 0px;
  overflow: hidden;
}

.nav { 
    display: flex;
    width: 100%;
    height: 50%;
    justify-content: center;
}
.nav > nav {
    padding: 20px;
    align-self: center;
    border: 1px solid #000000;
    margin: 0 10px 0 0;
    transition: background 0.2s ease-in-out;
}

.nav > nav:hover {
    cursor: pointer;
    background: #cecece;
}

nav.active-nav.active, .active {
  background: #cecece;
}

.contentDetails {
    margin: 40px;
    height: 100%;
    display: block;
    position: absolute;
}

/* SLIDE IN OUT */

.slide-enter {
  transform: translate(100%, 0%);
  opacity: 1;
}

.slide-leave-to {
  transform: translate(-100%, -0%);
  opacity: 0;
}

.slide-leave-active,
.slide-enter-active {
  position: absolute;
  transition: transform 1s ease-in-out, 
              opacity 0.2s ease-in-out;
}

  

/* FLIP IT */
.flipit-enter,
.flipit-leave-to {
  opacity: 0;
  transform: rotateY(50deg);
}
.flipit-enter-to,
.flipit-leave {
  opacity: 1;
  transform: rotateY(0deg);
}
.flipit-enter-active,
.flipit-leave-active {
  transition: opacity, transform 200ms ease-out;
}


/* SLIDE UP */
.slideup-enter,
.slideup-leave-to {
  opacity: 0;
  position: absolute;
  right: -9999px
}

.slideup-enter-to,
.slideup-leave {
  opacity: 1;
  right: 0;
}

.slideup-enter-active,
.slideup-leave-active {
  transition: right, transform 200ms ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">

                        <!-- NAVIGATION -->
                        <div class="nav">
                            <nav v-for="nav in navs"
                                v-bind:key="nav"
                                v-bind:class="['nav-button', { active: currentNav === nav }]"
                                v-on:click="currentNav = nav">
                                {{ nav }}
                            </nav>
                        </div>
                    
                        <!-- CONTENT COMPONENT-->
                        <transition name="flipit">
                            <component
                                v-bind:is="contentComponent"
                                class="contentDetails">
                            </component>
                        </transition>

                    </div>

I am expecting to see either the product banner or blog-post displayed from the product-banner or blog-post component inside the nav-home component.

Upvotes: 0

Views: 96

Answers (1)

skirtle
skirtle

Reputation: 29132

You need to pass banners and posts as props to your nav-home component too.

<component
    :is="contentComponent"
    class="contentDetails"
    :banners="banners"
    :posts="posts"
>

Vue.component('nav-home', {
  props: ['banners', 'posts'],
  template: `
    <div>
      <h1> Home component </h1>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

      <!-- banner -->
      <product-banner
        v-for="banner in banners"
        :key="'banner' + banner.id"
        :banner="banner"
      />

      <!-- post -->
      <blog-post
        v-for="post in posts"
        :key="'post' + post.id"
        :post="post"
      />
    </div>
  `
})

Vue.component('blog-post', {
  props: ['post'],
  template: `<h3>{{post.title}}</h3>`
})

Vue.component('product-banner', {
  props: ['banner'],
  template: `
    <div id="product-banner">
      <div class="product-container">
        <h2>{{banner.title}}</h2>
        <p>{{banner.content}}</p>
      </div>
    </div>
  `
})

new Vue({
  el: '#app',
  data: {
    currentNav: 'Home',
    navs: ['Home'],
    posts: [
      {
        id: 1,
        title: 'Title one'
      },
      {
        id: 2,
        title: 'Title two'
      },
      {
        id: 3,
        title: 'Title three'
      }
    ],
    banners: [
      {
        id: 1,
        title: 'Title 1',
        content: 'Content 1'
      },
      {
        id: 2,
        title: 'Title 2',
        content: 'Content 2'
      },
      {
        id: 3,
        title: 'Title 3',
        content: 'Content 3'
      }
    ]
  },
  computed: {
    contentComponent: function() {
      return 'nav-' + this.currentNav.toLowerCase()
    }
  }
})
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">

  <!-- NAVIGATION -->
  <div class="nav">
    <nav
      v-for="nav in navs"
      :key="nav"
      :class="['nav-button', { active: currentNav === nav }]"
      @click="currentNav = nav"
    >
      {{ nav }}
    </nav>
  </div>

  <!-- CONTENT COMPONENT-->
  <transition name="flipit">
    <component
      :is="contentComponent"
      class="contentDetails"
      :banners="banners"
      :posts="posts"
    >
    </component>
  </transition>

</div>

I've made a few others tweaks too:

  1. Shortening v-bind: to : and v-on: to @.
  2. Changing the key attributes so that banners and posts wouldn't clash with each other.
  3. Renaming the prop for blog-post from posts to post, updating the template accordingly.

Upvotes: 1

Related Questions