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 ...
For further actions, you may consider blocking this person and/or reporting abuse
There is a simpler way to achieve this, which is probably even less known, without the use of an extra component and it goes like this:
For those that may now be thinking, "Hey, I didn't know there was an undocumented
:set
in Vue", there isn't. What I'm doing here is relying on the fact that Vue will evaluate the JavaScript of any bound attributes and I just chose to invent an attribute called:set
.As a reminder, a bound attribute is one that is prefixed with a
:
orv-bind:
. The JavaScript expression inside the double quotes will be evaluated in the context of the current component and theitem
variable will still be available outside of thev-for
in which it is being set. So, it's not a local variable, as such, rather, it's a locally assigned component scope (non-reactive) variable.Do note that this attribute does not have to be declared in your data component first. However, if you don't declare it, it will not be reactive. In the example above it does not matter to me if it's reactive, but it's something to keep in mind if you use this pattern
Here is a fork of Florian's code (thank's Florian) showing the pattern at work.
codesandbox.io/s/6nwyw3zzwz
This may work in JS but TypeScript can't make heads or tails of this. The referenced function will be marked as un-used, causing a TS error, and the unknown variable will also be marked as unknown. You can define the var in the
<script>
block, but it will still leave the previous error.A better approach may be to use a WeakMap in the getUserData function. Like:
Repeated calls to
getUserData
within the loop, then, will instantly return the item, and once we're out of that iteration of the loop, the cached result will be garbage collected.I didn't put in TypeScript annotations but TS handles this approach much better as well.
This is definitely a clever hack. A little bit dirty, but absolutely clever. 😁
Thanks. I would like to add, though, this is neither dirty nor a hack.
Vue does allow us to create local variables on
this
and just cautions that they won't be reactive (I read it somewhere in the docs).As for setting values, well, it's just like an event handler, where one would do exactly the same:, for example:
@click="item=$event"
I know it feels like a hack, at first looks, because it's too simple, but it's legit.
Perhaps, I cheated a bit by using an attribute named
:set
;)I think my perception of this as a "hack" mostly refers to "using assignment expressions as attributes". It just doesn't feel right (to me, personally). Sure, it's a thing we've done forever in
on*
event attributes, but at least the semantics of events do match this better than the semantics of vanilla attributes.That's pretty much the same as
let foo; while (foo = expression) { /* use foo */ }
— it's a thing that is frowned upon by many readers and linters, but it's used because the clean way would be annoyingly verbose.I hear you.
How does this solve the fact that the call is not memoized?
It doesn’t. It just helps to avoid using deeply nested object references or making calls to the same function several times. You could use a computed, without a parameter, if you want caching.
Hmmm...no you can't use a computed here. It's in a loop. That was the whole point here.
Indeed, you’re right. A normal computed wouldn’t have access to the v-for parameter here and a parametrized computed wouldn’t be cached.
If you need caching then you will need a different technique.
You could declare a data variable and then assign the v-for iterator to it, like I have shown in my example. In this way, a computed would have access to that data variable with the iterator’s value. Would this work for you?
My way would be to create computed property with stored results of
getUserData
function execution:After that is can simply be used like:
More Vue-ish as for me.
Yes, this is the obvious one and should always be preferred where applicable.
Template variables are a better (as in "more ergonomical") fit when you don't need to process all the array items and don't want to duplicate logic.
I'm a newbie when talking about Vue - well, programming in general, but your solution makes sense. I'll try to make use of it in an Angular based app I'm working on at office, where I've encountered the need of keeping some computed values close. Thanks for sharing!
Glad it helped. The approach was heavily inspired by React's render props. I have no clue though whether it's applicable to the Angular world.
I guess we'll find out 😁
How come the user data isn't "gotten" up front? I mean, creating calls for data in a loop (even in a program) is a bad pattern to begin with, isn't it?
Scott
The user data thing is a completely made up example, I didn't come up with something better that's pragmatic and also immediately "clicks" with the reader.
But yeah, if you mean network requests with "calls for data", that would not necessarily be a bad idea in a loop, but they certainly do not belong inside a template. In a real world example, the kind of data to store usually is some calculation or data structure creation you don't need in each iteration of the loop.
You could certainly create all needed data upfront and move the logic to other places (e.g. by memoizing or refactoring to additional components, as mentioned in the post) but to me it actually happens quite a lot that such an approach might be "cleaner" but is not worth sacrificing the amount of clarity and ease of use.
Yup. It's a bad example. Glad you agree. User data for the front end should not be in different places. In other words, there is no reason to have an array of users to then have to get the data to fill that array in a loop. That gathering of data should be done before the data reaches the loop.
If you pass anything to child components being looped over in a v-for, that passed data should be relatively small bits of data already "gotten". Or it can be data that can be broken down through the hierarchy of components. Most definitely each smaller component should NOT be calling for more data. That's sidestepping one-way data flow.
So, I'll be so bold to say, you probably couldn't find a practical use case, because there isn't one, because trying to call for data from passed in props or data is simply a poor pattern. If you are finding you need to do that, you are somehow starting off with an incorrect mental model of SFCs, component hierarchy and proper reactive data flow.
Scott
This can be useful to prevent v-for and v-if in one element.