简体   繁体   中英

Vue component renders only the first element in the template DOM

I have a Vue component inside a Vue loop on my website. Here's the JS file:

Vue.component('fb-question-text', {
  props: ['question'],
  template:
    '<label>Prompt</label><input type="text" class="form-control" v-model="question.prompt"><a href="javascript:void" class="fb-remove">Remove</a>'
});

var questionList = new Vue({
  el: '#questions',
  data: {
    questions: [
      {
        type: 'text',
        id: 'question1',
        prompt: ''
      },
      {
        type: 'choice',
        id: 'question2',
        prompt: '',
        choices: ['', '']
      }
    ]
  }
});

This is what my HTML file looks like:

<ul id="questions">
    <li v-for="(question, index) in questions">
        <h4>Question {{ index + 1 }}</h4>
        <fb-question-text v-if="question.type === 'text'" :question="question"></fb-question-text>
    </li>
</ul>

As you can see, I am trying to render the fb-question-text component if the question.type is of type "text". While the <li> elements do render in the page, the component template does not render entirely. Only the first DOM element inside the template is rendered (in this case, the <label> element). The input box and <a> that are inside the component do not get rendered for some reason. When I remove the label, the input box gets rendered but nothing after it.

Can someone tell me what is going on?

Templates must have a single root element.

<span>
  <label>Prompt</label>
  <input type="text" class="form-control" v-model="question.prompt">
  <a href="javascript:void" class="fb-remove">Remove</a>
</span>

Your fb-question-text is only allowed to have one root element. You need to wrap that in a div.

Add <div> around template html

 Vue.component('fb-question-text', { props: ['question'], template: '<div><label>Prompt</label><input type="text" class="form-control" v-model="question.prompt"><a href="javascript:void" class="fb-remove">Remove</a></div>' }); var questionList = new Vue({ el: '#questions', data: { questions: [ { type: 'text', id: 'question1', prompt: '' }, { type: 'choice', id: 'question2', prompt: '', choices: [ '' , '' ] } ] } }); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.11/vue.js"></script> <ul id="questions"> <li v-for="(question, index) in questions"> <h4>Question {{ index + 1 }}</h4> <fb-question-text v-if="question.type === 'text'" :question="question"></fb-question-text> </li> </ul> 

For a similar question I purpose an other answer : Vue js error: Component template should contain exactly one root element

copy paste here :

if, for any reasons, you don't want to add a wrapper (in my first case it was for <tr/> components), you can use a functionnal component.

Instead of having a single components/MyCompo.vue you will have few files in a components/MyCompo folder :

  • components/MyCompo/index.js
  • components/MyCompo/File.vue
  • components/MyCompo/Avatar.vue

With this structure, the way you call your component won't change.

components/MyCompo/index.js file content :

import File from './File';
import Avatar from './Avatar';   

const commonSort=(a,b)=>b-a;

export default {
  functional: true,
  name: 'MyCompo',
  props: [ 'someProp', 'plopProp' ],
  render(createElement, context) {
    return [
        createElement( File, { props: Object.assign({light: true, sort: commonSort},context.props) } ),
        createElement( Avatar, { props: Object.assign({light: false, sort: commonSort},context.props) } )
    ]; 
  }
};

And if you have some function or data used in both templates, passed them as properties and that's it !

I let you imagine building list of components and so much features with this pattern.

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