Skip to content

Instantly share code, notes, and snippets.

@loilo
Last active December 5, 2018 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loilo/7ab6f6e81950c1baf6eaed1b929ca831 to your computer and use it in GitHub Desktop.
Save loilo/7ab6f6e81950c1baf6eaed1b929ca831 to your computer and use it in GitHub Desktop.
(An Approach to) Vue.js Template Variables

(An Approach to) Vue.js Template Variables

This has first been published as an article on dev.to.

The Problem

From time to time, I have the need to temporarily store the results of a method call in Vue templates. This is particularly common inside loops, where we cannot easily use computed properties.

Basically what we'd want to avoid is this:

<!-- List.vue -->
<ul>
  <li v-for="id in users" :key="id">
    <img :src="getUserData(id).avatar"><br>
    🏷️ {{ getUserData(id).name }}<br>
    🔗 {{ getUserData(id).homepage }}
  </li>
</ul>

Common Solutions

We could describe this problem as "computed properties with arguments", and it already has some established solutions out there:

Outsource Components

The pretty much canonical way is done through refactoring: We could outsource the <li> items into their own <ListItem> component.

That component would receive the id as a prop and store the according metadata in a computed property which is then cached by Vue until it needs to be re-evaluated.

<!-- List.vue -->
<ul>
  <ListItem v-for="id in users" :key="id" :id="id" />
</ul>

<!-- ListItem.vue -->
<li>
  <img :src="metadata.avatar"><br>
  🏷️ {{ metadata.name }}<br>
  🔗 {{ metadata.homepage }}
</li>

However, this approach can be pretty tedious to write and maintain: all pieces of data we need inside each list item have to be passed down to the <ListItem> as props.

It can also be hard to follow as a reader — particularly if the <ListItem> component is very small. Then it may easily contain four lines of template code followed by 25 lines of props definition boilerplate.

Memoize Method Results

We could also memoize the results of getUserData().

However this can be tedious to implement as well, it usually only works with serializable input data — and of all approaches, adding another layer of memoization on top of Vue feels like suiting the Vue way™ the least.

My Approach

For my projects, I like to use another (less obvious, and AFAICT less common) approach: I create a helper component I call <Pass>.

It's really, really tiny:

const Pass = {
  render() {
    return this.$scopedSlots.default(this.$attrs)
  }
}

Basically this is a placeholder component which does not render a DOM element itself but passes down all props it receives to its child.

So, let's rewrite our list with the <Pass> helper:

<!-- List.vue -->
<ul>
  <Pass v-for="id in users" :key="id" :metadata="getUserData(id)">
    <li slot-scope="{ metadata }">
      <img :src="metadata.avatar"><br>
      🏷️ {{ metadata.name }}<br>
      🔗 {{ metadata.homepage }}
    </li>
  </Pass>
</ul>

This will only evaluate getUserData() once: when <Pass> is rendered. Nice and clean, isn't it?

Also, here's a CodeSandbox where you can fiddle around with the example I described.

Caveats

To be fully honest, there are a few drawbacks to this approach:

  • The helper component utilizes a scoped slot for passing data. This means, <Pass> can only have exactly one child component.
  • Another limitation to this approach is that the markup injected into the slot must render a real DOM node. We can't just set the slot-scope on something like a <template>.

That's it. I hope this helps simplify your Vue templates!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment