(An Approach to) Vue.js Template Variables
Tags: vue, javascript
Published: 2018-12-05 00:34:08

---
title: (An Approach to) Vue.js Template Variables
published: true
description: Ever had to deal with caching method calls inside Vue templates?
tags: vue,javascript
---

## 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:
```vue
<!-- 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.

```vue
<!-- 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 <abbr title="as far as I can tell">AFAICT</abbr> less common) approach: I create a helper component I call `<Pass>`.

It's really, really tiny:
```js
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:

```vue
<!-- 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:

{% codesandbox 1v50nxwx77 %}

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

* The helper component utilizes a [scoped slot](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots) 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!