Skip to content

enable <template> as component root element when it contains only one logical node #5758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
caikan opened this issue May 26, 2017 · 32 comments
Closed

Comments

@caikan
Copy link

caikan commented May 26, 2017

What problem does this feature solve?

From compiling template error message:
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
But Cannot use as component root element because it may contain multiple nodes.

So I can't use <template> as component root element even if it contains only one node.

What does the proposed API look like?

Allow the following code as component root.

<template v-if="conditionA">
	<div v-if="conditionC"></div>
	<div v-else-if="conditionD"></div>
	<div v-else></div>
</template>
<template v-else>
	<div></div>
</template>

Do not allow the following code as component root.

<template>
	<div v-if="conditionA"></div>
	<div v-if="conditionB"></div>
</template>

@larryu
Copy link

larryu commented May 27, 2017

@caikan For the second case, you can change the code as below.

<template>
	<div>
		<div v-if="conditionA"></div>
		<div v-if="conditionB"></div>
	</div>
</template>

Because your code may cause two div element in template which is not allowed in vue.
And you first sample code only results in one template and one div in template.

@phillip-haydon
Copy link

I'm curious, why can't the template contain multiple nodes and just become <div> , and avoid the nested single div. If the reasoning is so that you can pick the element you want as the root, can't that be solved by <template as="footer">??

@caikan
Copy link
Author

caikan commented Jun 10, 2017

@larryu Thanks. But sometimes I want to use the wrapper elements as few as posiblle.

@yyx990803
Copy link
Member

This would technically require much more complex analyzing of the template structure and I'm not sure if it's worth it when you can achieve the equivalent with this:

<div v-if="!conditionA"></div>
<div v-else-if="conditionC"></div>
<div v-else-if="conditionD"></div>
<div v-else></div>

@caikan
Copy link
Author

caikan commented Jul 4, 2017

If the implementation process is complex, can we temporarily use a warning instead of a compilation error and allow its first child node as the root element?

BTW: <keep-alive> can be used as component root element now(Vue v2.3.4), but the non-first child node will be discarded. I think there should be a warning here, and <template> can be treated in the same way.

OK, I think I may use <keep-alive> instead of <template> as the root elements wrapper now.

@fritx
Copy link

fritx commented Oct 26, 2017

+1, feature request.

had to make <th>s, <td>s as root elements of <template>, otherwise <table>'s layout would break.

<!-- some-table.vue -->
<table>
  <tr>
    <copy-first :arr="arr">
      <template slot="elem" slot-scope="s">
        <td>{{s.index}}</td>
      </template>
    </copy-first>
  </tr>
</table>

<style>
tr th:nth-child(1), tr th:nth-child(2),
tr td:nth-child(1), tr td:nth-child(2) {
  /* hack of fixed column */
}
</style>
<!-- copy-first.vue -->
<template>
  <slot v-if="arr[0]" name="elem" :index="0" :elem="arr[0]"></slot>
  <slot v-for="(elem, i) in arr" :key="i" name="elem" :index="i" :elem="elem"></slot>
</template>

<script>
export default {
  name: 'copy-first',
  props: { arr: Array }
}
</script>

@asolopovas
Copy link

asolopovas commented Nov 14, 2017

+1 When it comes to generating tables it simplifies things

@brandonferens
Copy link

+1

Perhaps something as simple as allowing <root>...</root> as the root component wrapper? If that element is used it could just return everything within the element, but not the root wrapper itself?

@djschilling
Copy link

React has a nice implementation of this in the current version: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html

@vuejs vuejs deleted a comment from agonzalezml Dec 12, 2017
@lichenhao
Copy link

How about add fragment for root like a Web Component with shadow root.

@marktnoonan
Copy link

Another use for this is that am refactoring a component in order to make it easier to maintain. It's a form and various parts of it fall on a CSS grid. The extra wrapper required when I group several related elements in the same component is causing the grid to treat that whole component as one grid-item. I'm sure I can work around that, but hope we end up with a fragment syntax like react's (if we don't already have such a thing?)

@maxnorth
Copy link

maxnorth commented Mar 12, 2018

My primary need for this is to maintain valid HTML. I have a component that generates table rows with different rendering depending on different logic, and I can't give the <tr>'s a div parent without violating HTML rules. Accessibility is a priority of this table, so good HTML structure and semantics are important. I'm fairly certain I can satisfy my need with render(), but it's a bummer that I have to sacrifice a clean declarative implementation.

@marktnoonan
Copy link

@njleonzhang For what it's worth, though I haven't tried it yet, I recently learned about portal-vue feel a workaround may be possible and relatively easy with it for many cases where the component structure Vue needs is different to the DOM structure we want to actually end up with (https://github.com/LinusBorg/portal-vue). I'm looking forward to playing with it. I will post a demo if I get time to create a clean example, but wanted to go ahead and share the library anyway so others can maybe check it out.

@njleonzhang
Copy link

njleonzhang commented May 19, 2018

@markbrouch currently I make all grid items in one component and provide a layout props to customize the component. I will try portal-vue next time, and tnx for your information.

@njleonzhang

This comment has been minimized.

@Frizi
Copy link

Frizi commented Jul 12, 2018

The whole issue could be irrelevant if fragments were supported instead. What's the reason why it's disallowed really? The patching algorithm already works on arrays, it's just the fact that it starts from node to iterate on it's children, instead of starting iterating on array to visit all nodes in this array. I had similar issue with vuelidate once, where I ported simplified version of DOM patching into validation tree to find differences in "virtual validation tree". It was required to support multiple nodes on each level, as there were no guarantees about data shapes. It turned out it was pretty easy to just turn the algorithm upside down to treat the array as entry point.

@sgf
Copy link

sgf commented Jan 19, 2019

This is a very important technology.
Because vue currently prohibits more than one element as the root element of the template.
We are unable to create a multiple-root-element template.
but if vue allow the like this:

<script type="text/x-template" id="row-template">
    <template>
        <tr>
            <td v-for="col in cols">{{row[col.col]}}</td>
            <td v-on:click="ep=!ep"> <span v-text="ep?'collapse':'expand'"></span></td>
        </tr>
        <tr v-show="ep">
            <td v-bind:colspan="cols.length+1">Content of collapse Area</td>
        </tr>
    </template>
</script>

thats will be very good way to create multi root-element in template.

We do not need to add an extra element to wrap it.
Because the disposal of surplus extra element is a waste of time.
also, the extra element will degrade performance.

Actually, this issue has been referred to by more than one issue.
#7088
#8191
#655
But still not been solved.

@yyx990803

Can solve it as soon as possible.
If this issue cannot resolve in 2.X, Please inform all users, this issue is 2. X version will never be solved.

Finally, in any case Thanks for vue community.👍

@Jlomaka

This comment has been minimized.

@posva

This comment has been minimized.

@vuejs vuejs deleted a comment from Yangfan2016 Feb 26, 2019
@vuejs vuejs deleted a comment from pdrolima Feb 26, 2019
@vuejs vuejs deleted a comment from njleonzhang Feb 26, 2019
@vuejs vuejs deleted a comment from phillip-haydon Feb 26, 2019
@vuejs vuejs deleted a comment from EmilMoe Feb 26, 2019
@brianjlacy
Copy link

+1 Rendering tables is the most common practical scenario where this would be useful, and I strongly agree with this request. <tr> and <td> tags can't be wrapped in a <div>.

Is this still under consideration? Or is there a workaround I don't know about yet?

@RPainter8West
Copy link

Another possible solution would be for vue to have a switch statement for the contents like

   <template v-switch="myComponentType">
        <componentTypeA v-case="componentTypeA" ...></componentTypeA> 
        <componentTypeB v-case="componentTypeB" ...></componentTypeB>
   </template>

@marktnoonan
Copy link

@RPainter8West If I understand your need correctly, I think this is already possible with Vue's built-in dynamic component feature: https://vuejs.org/v2/guide/components.html#Dynamic-Components, though maybe docs don't explore the potential of this enough. I've used this pattern in the past:

<template>
<component :is="someProp"></component>
</template>

Where someProp is any component's name, and there's some other logic or styling in the file that's reusable across multiple inner components. The named component will render in place of the <component> element.

So for the use case where a person wants to render only one of a group of components, with no extra wrapper element, I think it's already covered.

@tsangaris
Copy link

Totally agree with @maxnorth .

I am trying to implement a simple table with data coming from Algolia Vue InstantSearch and the tr is wrapped with a div. This breaks the table and the solution is to work with custom widgets (connectors). This is a shame since HTML tables are one of the most used elements.

@bci24
Copy link

bci24 commented Nov 1, 2019

the same problem here with tr that is wrapped with a div

@douglasg14b
Copy link

The requirement of wrapping with a div, or some other element, breaks content flow all the time.... There has got to be a way around this?

@iamvitali
Copy link

All I honestly want from a <template> is:

Parent:

...
    <ul>
        <li>parent list element</li>
        <li>parent list element</li>
        <li>parent list element</li>
        <child></child>
        <li>parent list element</li>
        <li>parent list element</li>
        <li>parent list element</li>
    </ul>
...

Child:

    <template>
        <li>child list element</li>
        <li>child list element</li>
    </template>

All in order to get this beauty:

...
    <ul>
        <li>parent list element</li>
        <li>parent list element</li>
        <li>parent list element</li>
        <li>child list element</li>
        <li>child list element</li>
        <li>parent list element</li>
        <li>parent list element</li>
        <li>parent list element</li>
    </ul>
...

If I use a work-around like such then it breaks my CSS:

    <template>
        <div>
            <li>child list element</li>
            <li>child list element</li>
        </div>
    </template>

because I get this instead:

...
    <ul>
        <li>parent list element</li>
        <li>parent list element</li>
        <li>parent list element</li>
        <div>
            <li>child list element</li>
            <li>child list element</li>
        </div>
        <li>parent list element</li>
        <li>parent list element</li>
        <li>parent list element</li>
    </ul>
...

😢

@adrian-amaglio
Copy link

adrian-amaglio commented Mar 29, 2020

Did someone find a solution for this table problem ?

@f15u
Copy link

f15u commented May 12, 2020

As a (temporary) solution, you can use vue-fragment.

<template>
  <fragment>
    Your content here
  </fragment>
</template>

Output:

<!--fragment#1690ec9267a#head-->
  Your content here
<!--fragment#1690ec9267a#tail-->

@GeekyMonkey
Copy link

The good news is that Fragments are included in Vue 3 so we'll have multiple root elements in a template!

https://vueschool.io/articles/vuejs-tutorials/exciting-new-features-in-vue-3/

@niksajanjic
Copy link

Until Vue 3 version comes out I would strongly recommend using a method proposed by @FedericoBiccheddu.

If for any reason, you don't want to use the third party lib, you can use this hack:

<template v-if="true">
  <p>Sibling 1</p>
  <p>Sibling 2</p>
</template>

Unfortunately, this solution is limiting (same as in conditional rendering), so you can't return a component that has 2 siblings without root element. This won't work:

<template>
  <template v-if="true">
    <p>Sibling 1</p>
    <p>Sibling 2</p>
  </template>
</template>

This will:

<template>
  <div>
    <template v-if="true">
      <p>Sibling 1</p>
      <p>Sibling 2</p>
    </template>
  </div>
</template>

@GTCrais
Copy link

GTCrais commented Aug 20, 2020

As a (temporary) solution, you can use vue-fragment.
...

This deserves a lot more upvotes!

@posva
Copy link
Member

posva commented Dec 15, 2021

Duplicate of #7088

Here are the current alternatives: #7088 (comment)

@posva posva marked this as a duplicate of #7088 Dec 15, 2021
@posva posva closed this as completed Dec 15, 2021
@vuejs vuejs locked and limited conversation to collaborators Dec 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests