djeetee
djeetee

Reputation: 1837

Understanding components nesting in VueJS

learning VueJS and going through some of the tutorials online including the guide on vuejs.org and I'm having one hell of a time understanding how components can be nested and have them communicating through props.

Simple example seem to be ok but the following code (slightly tweaked but pretty much out of the VueJS guide) is giving me trouble.

I cannot seem to be able to have 'blog-item' nested within 'blog-items.

I would appreciate it if someone could explain the big picture of how components could be nested along with the use the v-for directive.

I have gone through many tutorials and everything seems to work one the component is nested inside the top-level 'app' component that's supplying the data but I cannot seem to be able to translate that into the scenario below.

As a newbie, I could be missing a key concept or totally off the rails understanding Vue :)

hope you can help.

thanks

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Components Basics - from vuejs.org</title>
    <!-- development version, includes helpful console warnings -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">

      <!-- This works. I get it. -->
      <div id="components-demo">
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
      </div>

      <hr>

      <!-- This works too and I get it. -->
      <div id="blog-post-demo-simple">
        <blog-post-simple title="My journey with Vue"></blog-post-simple>
        <blog-post-simple title="Blogging with Vue"></blog-post-simple>
        <blog-post-simple title="Why Vue is so fun"></blog-post-simple>
      </div>

      <hr>

      <!-- This is where I'm totally confused -->
      <!-- How do I structure this to make sure blog-items is binding the 'post'  -->
      <!-- correctly? What is not clear to me is where the directives should be placed -->
      <!-- Vue keeps complainig with the following: -->
      <!-- Property or method "posts" is not defined on the instance but referenced during render -->
      <blog-items>
        <blog-item
          v-for="post in posts"
          v-bind:key="post.id"
          v-bind:post="post">
        </blog-item>
      </blog-items>

      <hr>

    </div>

    <script>

      // Define a new component called button-counter. Cool. No problem here.
      Vue.component('button-counter', {
        data: function () {
          return {
            count: 0
          }
        },
        template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
      })

      // This is also clear.
      Vue.component('blog-post-simple', {
        template:
        '<h3>{{title}}</h3>',
        props: {
          title: {
            type: String,
            required: true
          }
        }
      })

      Vue.component('blog-items', {
        data() { return {
            posts: [
              { id: 1, title: '1. My journey with Vue' },
              { id: 2, title: '2. Blogging with Vue' },
              { id: 3, title: '3. Why Vue is so fun' }
            ]
          }
        }
      })

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

      var app = new Vue({
        el: '#app'
      })

    </script>
  </body>
</html>

Upvotes: 3

Views: 1424

Answers (1)

Husam Elbashir
Husam Elbashir

Reputation: 7177

Remember when you access a property in a template you're fetching that property from the component that uses that template. In this case it's your root #app component. Since that component doesn't have a property or method with the name posts then Vue is going to complain. What you need to do is move that part within the blog-items component's template since that component is holding your posts.

So what you need to do is this ..

    <!-- This is where I'm totally confused -->
    <!-- How do I structure this to make sure blog-items is binding the 'post'  -->
    <!-- correctly? What is not clear to me is where the directives should be placed -->
    <!-- Vue keeps complainig with the following: -->
    <!-- Property or method "posts" is not defined on the instance but referenced during render -->
    <blog-items></blog-items>

Vue.component('blog-items', {
    template: `
    <div>
        <blog-item v-for="post in posts" v-bind:key="post.id" v-bind:post="post" />
    </div>
    `,
    data() {
        return {
            posts: [
            { id: 1, title: '1. My journey with Vue' },
            { id: 2, title: '2. Blogging with Vue' },
            { id: 3, title: '3. Why Vue is so fun' }
            ]
        }
    }
})

Otherwise you'll have to resort to using Scoped Slots, which allow you to expose properties/methods from the child component's scope to the parent ..

<blog-items>
    <template slot-scope="{ posts }">
        <blog-item v-for="post in posts" v-bind:key="post.id" v-bind:post="post">
        </blog-item>
    </template>
</blog-items>

Vue.component('blog-items', {
    template:`
    <div>
        <slot :posts="posts"></slot>
    </div>`,
    data() {
        return {
            posts: [
            { id: 1, title: '1. My journey with Vue' },
            { id: 2, title: '2. Blogging with Vue' },
            { id: 3, title: '3. Why Vue is so fun' }
            ]
        }
    }
})

I found this especially helpful in understanding how scoped slots work.

Upvotes: 2

Related Questions