funkyfly
funkyfly

Reputation: 1174

Avoid repeating pattern in vue template

I have this huge annoying component that needs to be repeated many times in the parent template because the parent template is using v-if. Here is the component code:

<SelectCard
  v-for="(channel, index) in category.visibleChannels"
  :key="index + '-' + channel.id"
  :channel="channel"
  :channel-selected="isSelected(channel.id)"
  :read-more-details="channelInfoDetails"
  @select="onAddChannel"
  @deselect="onRemoveChannel"
  @read-more-changed="setChannelInfoDetails"
/>

The only thing that changes between each time I render the template is what array I loop over.... Here is a simplified version of the problem:

<template>
<div
    ref="channels"
    class="channels"
  >
    <div v-if="showCategories">
      <div
        v-for="category in sliderCategories"
        :key="category.name"
      >
        <h3 v-text="category.name" />
        <div
          v-if="category.showAll"
          class="channel-list show-all"
          :class="channelListSize"
        >
          <ul>
            <SelectCard looping over category.contents  />
          </ul>
        </div>
        <ChannelSlider
          v-else
          :category="category"
          @visible-updated="setVisibleChannels"
        >
          <SelectCard looping over category.visibleChannels  />
        </ChannelSlider>
        <div class="show-all-link">
          <a
            :class="category.showAll?'arrow-up':'arrow-down'"
            class="link"
            @keyup.enter="toggleShowAll(category.name, !category.showAll)"
            @click="toggleShowAll(category.name, !category.showAll)"
            v-text="showAllText(category.showAll)"
          />
        </div>
      </div>
    </div>
    <div v-else>
      <div v-if="showNoSearchResult">
        <SomeComponent with some props/>
      </div>
      <div :class="channelListSize" class="channel-list">
        <ul>
          <SelectCard looping over updatedChannels  />
        </ul>
      </div>
    </div>
    <div
      ref="someref"
      class="someClass"
      :style="{top: channelInfoDetails.top + 'px', position: 'absolute'}"
    >
      <AnotherComponent with some props/>
    </div>
  </div>
</template>

So my template becomes HUGE because the SelectCard code has so many props.

Is there a way I can put SelectCard in a method in the parent code, so that I can just call a function with the array to use or something? Or is there another solution that I don't know of?

Upvotes: 0

Views: 1328

Answers (2)

skirtle
skirtle

Reputation: 29092

I don't think there's a solution here that is as simple as you might like. But there are some possibilities.

You can reduce it down slightly by using the object form of v-bind and v-on. For the v-bind you'd need to introduce a method to return the object as your props depend on channel and index, so they would need passing to the method. This would cut it down a bit but it isn't great. The object form of the is attribute might also be an option. That might squeeze it down a little further but at the expense of clarity.

Another approach would be to introduce another component and then use slots for the SelectCard. e.g.:

<div>
  <div v-if="conditionA">
    <div v-if="conditionA-A">
      <slot />
    </div>
    <div v-else>
      <slot />
    </div>  
  </div>
  <div v-else>
    <div v-if="conditionB-A">
      <slot />
    </div>
    <div v-else>
      <slot />
    </div>  
  </div>
</div>

You'd then pass the SelectCard in as the slot contents, with a computed property to make the array dynamic.

One problem with this approach is that you may find yourself having to pass a lot of stuff around between the various layers of component to get it working.

A further option is to convert everything to a render function. You definitely can do what you're trying to do using a render function but that would come at the expense of having to forgo using a template. Whether that is really a problem would depend on the complexity of the rest of the template.

Upvotes: 2

Michael
Michael

Reputation: 5048

Put all the logic of the v-if's into a computed property that returns the correct array that you want to pass as . props to the SelectCard something like:

<SelectCard :arr="arrayToRender"/>
...
computed: {
       arrayToRender(){
           if (ConditionA){ return Array_A}
           if ....
    }

Upvotes: 0

Related Questions