Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

In-template variable definition #73

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
wspl opened this issue Jul 10, 2019 · 21 comments
Closed

In-template variable definition #73

wspl opened this issue Jul 10, 2019 · 21 comments

Comments

@wspl
Copy link

wspl commented Jul 10, 2019

Sometimes you will encounter such a situation:

<template>
  <div class="container">
    <p class="row">{{item && item.a && item.a.b && item.a.b.hello}}</p>
    <p class="row">{{item && item.a && item.a.b && item.a.b.world}}</p>
    <p class="row">{{item && item.a && item.a.b && item.a.b.goodbye}}</p>
  </div>
</template>

These code with the same logic need to be written many times.
I hope there is a way to define local variables in the template, then the above code can be abbreviated as:

<template>
  <div class="container">
    <var name="b" :value="item && item.a && item.a.b"></var>
    <p class="row">{{b && b.hello}}</p>
    <p class="row">{{b && b.world}}</p>
    <p class="row">{{b && b.goodbye}}</p>
  </div>
</template>

or

<template>
  <div class="container" v-var:b="item && item.a && item.a.b">
    <p class="row">{{b && b.hello}}</p>
    <p class="row">{{b && b.world}}</p>
    <p class="row">{{b && b.goodbye}}</p>
  </div>
</template>

Q: Why not computed?
A: The item in the above code may come from v-for, so computed is not a feasible solution.

@posva
Copy link
Member

posva commented Jul 10, 2019

It was proposed in the past: vuejs/vue#7325
Currently you can achieve it with https://github.com/posva/vue-local-scope

@skyrpex
Copy link

skyrpex commented Jul 10, 2019

Probably you already know, but if you use JSX you could do this:

export default {
    render() {
        const b = this.item && this.item.a && this.item.a.b;
        return (
            <div class="container">
                <p class="row">{b && b.hello}</p>
                <p class="row">{b && b.world}</p>
                <p class="row">{b && b.goodbye}</p>
            </div>
        )
    }
}

@afontcu
Copy link
Member

afontcu commented Jul 15, 2019

Q: Why not computed?
A: The item in the above code may come from v-for, so computed is not a feasible solution.

You can also create a method that accepts a parameter, so you can use it in v-for loops.

@phiter
Copy link

phiter commented Jul 15, 2019

The issue with methods is that they aren't cached, so a bit less performatic.

@LinusBorg
Copy link
Member

LinusBorg commented Jul 18, 2019

Creating a local variable as proposed by OP would also not be cached.

I see, and a local variable could serve as an in-loop cache for method results where computed props can't be used.

@ReinisV
Copy link

ReinisV commented Jul 31, 2019

Q: Why not computed?
A: The item in the above code may come from v-for, so computed is not a feasible solution.

Whenever I hear someone say that, I think "this is a perfect situation for creating a component for that item!". That would give you the ability to use simple computed getters that can be cached.

A component should always have exactly one reason to exist and one mission to execute. This parent components mission is not knowing how to render the child items.

@LinusBorg
Copy link
Member

LinusBorg commented Jul 31, 2019

Well, component instantiations come at a price (memory-footprint & render performance), so sometimes it's better to render i list's items in the parent for that reason. There shuold be ways to keep these situations performant, an this RFC aims to address it.

@frehner
Copy link

frehner commented Sep 7, 2019

Forgive my ignorance, I'm new to the vue community.

Could the above be done with optional chaining as well? e.g.

<template>
  <div class="container">
    <p class="row">{{ item?.a?.b?.hello }}</p>
    <p class="row">{{ item?.a?.b?.world }}</p>
    <p class="row">{{ item?.a?.b?.goodbye }}</p>
  </div>
</template>

it's stage 3 and has a babel plugin

@LinusBorg
Copy link
Member

Not right now, as vue-es2015-template compiler doesn't aupport >es6 features

But that can be solved.

@michaeldrotar
Copy link

michaeldrotar commented Sep 8, 2019

I was dealing with a similar concern recently in my work so wanted to add 2 cents...

(My day job is in Ember so I'll pseudo-code the concepts)

Generally I want to pass a data model to my components.. like pass a user to a component for showing the user's badge or pass a meeting to a component for showing when and where and who's attending that meeting... it's just simpler to do <my-component meeting=meeting> than to do <my-component name=meeting.name date=meeting.date whatever=meeting.whatever ...>

I've also recently come to appreciate that this allows me to flatten out deeply nested data and to add logic around data that's not directly suited to my use case. So I recently did something like this:

// Component receives `model`
name = model.model_name
date = computed(() => (model.date && date_from_format(model.date, 'YYYY-MM-DD')) || undefined)
instructions = computed(() => (model.location && model.location.meeting_instructions) || undefined)

The same can be done for lists... if I need to display a list of model.users, I originally considered passing them to a nested component as well, but sometimes that's overkill.

users = computed(() => {
  return model.users.map((user) => {
    const spouse = model.spouse || {};
    return {
      name: user.name,
      spouseName: spouse.name
    };
  });
});

Now the template can iterate users without fear of accessing a nested property that might not exist and my component js continues to uphold the contract that it's explicitly handling all the data that the template needs.

@cawa-93
Copy link

cawa-93 commented Dec 24, 2019

Will this make the code much less clear? In my opinion, if you have a large component, it will be difficult to understand what the variable is, where it comes from if it is not in the data of component.

Much simpler code that doesn't have any "magic" variables:

<template>
  <div class="container" v-if="item && item.a && item.a.b">
    <p class="row">{{item.a.b.hello}}</p>
    <p class="row">{{item.a.b.world}}</p>
    <p class="row">{{item.a.b.goodbye}}</p>
  </div>
</template>

@woothu
Copy link

woothu commented Jan 31, 2020

Computed can also solve v-for performance problem, as items could be mapped to have additional keys for display purposes + you can use object destructuring.

Btw. most of times proper Item structurizing can solve this issue.

@chriswoodle
Copy link

chriswoodle commented Sep 19, 2021

I think I found some kind of way to accomplish in-template variables in vue 3. Here is an example where I create the "in-template variable" inside of a v-for. Basically declare an inline array and iterate over the single item.

<div v-for='item in items'>
    <div v-for="record of [cache.find(item.otherId).value]">
        <span v-if="record">{{ record.name }}</span>
    </div>
</div>

In this case cache.find(item.otherId) returns back a ref<{ name: string } | null>

I feel like it's its kinda hacky, and I'm not really sure the performance impact.

@unrevised6419
Copy link

Looking at Angular template, their *ngIf directive supports local scoping

<div *ngIf="condition as value">{{value}}</div>

@julie777
Copy link

The issue with methods is that they aren't cached, so a bit less performatic.

Which is why there are computed functions that do cache results. :-)

@liho00
Copy link

liho00 commented Sep 25, 2021

<div v-data="{x:'my temp variable'}">{{x}}</div>

@komonjlep
Copy link

I think I found some kind of way to accomplish in-template variables in vue 3. Here is an example where I create the "in-template variable" inside of a v-for. Basically declare an inline array and iterate over the single item.

<div v-for='item in items'>
    <div v-for="record of [cache.find(item.otherId).value]">
        <span v-if="record">{{ record.name }}</span>
    </div>
</div>

In this case cache.find(item.otherId) returns back a ref<{ name: string } | null>

I feel like it's its kinda hacky, and I'm not really sure the performance impact.

It's great how this approach can be used to cache cross-referenced (calculated) item when I have two v-for for matching correct item from big list. This way I don't need to re-evaluate fetching of correct item for each action separately, but can simply fetch item only once and refer to it.

It looks like this works correctly, even with events. I would hope to see this implement in more readable manner and without using v-for in hackish-way. Please consider implementing this as built-in-feature.

@dima-hx
Copy link

dima-hx commented Feb 11, 2022

There is also a way to set multiple variables in :set directive in such way:

                        <div :set="br = {variable1: someCalculation(), variable2: anotherCalculations()}">
                            <pre>{{br}}</pre>
                        </div>

https://stackoverflow.com/a/56249209/2114398

https://dev.to/pbastowski/comment/7fc9

@myWsq
Copy link

myWsq commented Feb 25, 2022

Svelte implements this feature and has good Typescript support.

https://svelte.dev/docs#template-syntax-const

@mon-jai
Copy link

mon-jai commented Mar 22, 2023

<div v-data="{x:'my temp variable'}">{{x}}</div>

I can't find any documentation on v-data.

@darrylnoakes
Copy link

I can't find any documentation on v-data.

It was just a suggestion for the syntax, I believe.

@vuejs vuejs locked and limited conversation to collaborators May 8, 2023
@posva posva converted this issue into discussion #505 May 8, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests