Skip to content
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

Optional chaining in templates does not seem to work #11088

Closed
DRoet opened this issue Feb 7, 2020 · 66 comments
Closed

Optional chaining in templates does not seem to work #11088

DRoet opened this issue Feb 7, 2020 · 66 comments

Comments

@DRoet
Copy link

DRoet commented Feb 7, 2020

Version

15.8.3

Reproduction link

https://template-explorer.vuejs.org/#%3Cdiv%20id%3D%22app%22%20v-if%3D%22obj%3F.a%22%3E%7B%7B%20msg%20%7D%7D%3C%2Fdiv%3E

Steps to reproduce

Use a v-if that uses optional chaining w/ @vue/cli version 4.2.0:

v-if="test?.length > 0"

What is expected?

no error is thrown

What is actually happening?

following error is thrown:

  Errors compiling template:

  invalid expression: Unexpected token '.' in

    test?.length > 0

  Raw expression: v-if="test?.length > 0"
@posva posva transferred this issue from vuejs/vue-loader Feb 7, 2020
@haoqunjiang
Copy link
Member

As said in the release notes:

// Note: scripts only, support in template expressions only available in Vue 3

https://vue-next-template-explorer.netlify.com/#%7B%22src%22%3A%22%3Cdiv%20id%3D%5C%22app%5C%22%20v-if%3D%5C%22obj%3F.a%5C%22%3E%7B%7B%20msg%20%7D%7D%3C%2Fdiv%3E%22%2C%22options%22%3A%7B%22mode%22%3A%22module%22%2C%22prefixIdentifiers%22%3Afalse%2C%22hoistStatic%22%3Afalse%2C%22cacheHandlers%22%3Afalse%2C%22scopeId%22%3Anull%7D%7D

@DRoet
Copy link
Author

DRoet commented Feb 7, 2020

ah derp, totally read over that comment

@haoqunjiang
Copy link
Member

Technically, to support such syntaxes in Vue 2, we need to:

  1. tweak the codegen to not modify such expressions (currently it converts obj?.a to obj ? .a)
  2. vue-template-es2015-compiler needs to be able to parse such syntaxes (not necessarily to transpile, to parse is enough)
  3. backport this commit to vue-loader 15 vuejs/vue-loader@4cb4474#diff-c735bef98c9338c75a676df1903d2afc
  4. get ready for the possible numerous bug reports

We don't have the capacity to implement it yet. But contributions are welcome.

@haoqunjiang haoqunjiang reopened this Feb 7, 2020
@jacekkarczmarczyk
Copy link

vuejs/core@8449a97#diff-1cb91d3fc9313f91590cd27606eade47R402

@McPo
Copy link

McPo commented May 15, 2020

Potential workaround includes using lodash's get as stated in #4638 (comment)

Another hack is to use eval.

As $eval was taken out in Vue 2, you'll have to create your own mixin so that it can be accessed in all components without having to import it into each one.

i.e.

Vue.mixin({
  methods: {
    $elvis: p => eval('this.'+p)
  }
});

Example template

<template>
<span>{{ $elvis('foo?.bar') }}</span>
</template>

Although its still no substitute for the real operator, especially if you have many occurrences of it

@Edmund1645
Copy link

Edmund1645 commented May 17, 2020

Although its still no substitute for the real operator, especially if you have many occurrences of it

@McPo Just how safe do you think this is? I'm about to use it in production code because I do not want to set computed properties for the numerous nested fields that may be undefined or null

@McPo
Copy link

McPo commented May 18, 2020

Although its still no substitute for the real operator, especially if you have many occurrences of it

@McPo Just how safe do you think this is? I'm about to use it in production code because I do not want to set computed properties for the numerous nested fields that may be undefined or null

As far as Im aware, it should be fine, as youre in control of the input. The only issues would be if the developer wasnt aware that its using eval and allows user input to it, but that doesn't really make sense in the case of the elvis operator.

It may have a performance impact though, in that Vue will probably recall the method on every update. I would also suspect theres some overhead in calling eval in the first place. Providing its not in a tight loop, realistically you're not likely to notice a difference.

@troxler
Copy link

troxler commented May 18, 2020

Please do not use eval for that. It generally is bad practice and in this case specifically, it breaks in all browsers that do not support optional chaining. Whether or not you are using Babel or any other transpiler is irrelevant as they cannot transpile strings.

A better way to access properties in a fail-safe way (like with optional chaining) is the following:

<template><div>
  {{getSafe(() => obj.foo.bar)}} <!-- returns 'baz' -->
  {{getSafe(() => obj.foo.doesNotExist)}} <!-- returns undefined -->
</div></template>

<script>
export default {
    data() {
        return {obj: {foo: {bar: 'baz'}}};
    },
    methods: {getSafe},
};
function getSafe(fn) {
    try { return fn(); }
    catch (e) {}
}
</script>

The getSafe function catches any exceptions and implicitly returns undefined if the property access fails. You could also create a mixin (or use the new composition API) to reuse that function.

@McPo
Copy link

McPo commented May 18, 2020

Please do not use eval for that. It generally is bad practice and in this case specifically, it breaks in all browsers that do not support optional chaining. Whether or not you are using Babel or any other transpiler is irrelevant as they cannot transpile strings.

A better way to access properties in a fail-safe way (like with optional chaining) is the following:

<template><div>
  {{getSafe(() => obj.foo.bar)}} <!-- returns 'baz' -->
  {{getSafe(() => obj.foo.doesNotExist)}} <!-- returns undefined -->
</div></template>

<script>
export default {
    data() {
        return {obj: {foo: {bar: 'baz'}}};
    },
    methods: {getSafe},
};
function getSafe(fn) {
    try { return fn(); }
    catch (e) {}
}
</script>

The getSafe function catches any exceptions and implicitly returns undefined if the property access fails. You could also create a mixin (or use the new composition API) to reuse that function.

True I forgot to highlight the issue that it requires optional chaining to be supported in the browser. (Which isn't a major issue in my case, although I probably will stop using it anyway)

@adjenks
Copy link

adjenks commented Sep 3, 2020

For deeply nested complex objects, this seems really important. I have objects that are five layers deep. I like the getSafe() method described by troxler, but I would prefer that chaining just work.
I mean... It wasn't "really important" to me before it became a feature in the language, but now that it's a feature, using getSafe() or other methods like that that I would use seems messy.
So, I don't mean to nag, I'm just a user sharing my priorities. Thanks for all your hard work guys.
Maybe I'll just try to prioritize the move to Vue 3.

@tvkit
Copy link

tvkit commented Oct 17, 2020

Using the following for a TS project:

  private s<T>(obj: T | undefined): T {
    return obj || ({} as T);
  }

with VUE expressions that look like:

<q-input 
  v-model="model.value"
  label="Eyeballs"
  type="number"
  :min="s(s(model).pirate).min"
  :max="s(model).max"
/>

Gets a bit nesty beyond the first property, but the linters are happy.

@Juraj-Masiar
Copy link

private s(obj: T | undefined): T {
return obj || ({} as T);
}

@tvkit that's not exactly good implementation as it won't work with any other falsy values like empty string, zero, false. So s('').length will give me undefined instead of zero.

@Sceat
Copy link

Sceat commented Oct 17, 2020

Using a function is outside the scope of this issue and not a viable replacement. However if someone need this kind of hack, use this 👇

const reducer = source => (object, property) => object?.[property] ?? undefined
const optional_chain = (...parameters) => {
	const [source, ...properties] = parameters
	return properties.reduce(reducer(source))
}
<div :foo="optional_chain({}, 'foo', 'bar', 'baz')" />

@tvkit
Copy link

tvkit commented Oct 18, 2020

@Juraj-Masiar Good point.

@Baldrani
Copy link

Baldrani commented Dec 1, 2020

@Sceat I actually really like the idea.

@edgarshurtado
Copy link

I guess the idea is to use optional chaining in Single File Components. But, funny enough, if you import the template from an HTML or PUG file, it works. I'd guess it shouldn't work either.

@Shinigami92
Copy link

Shinigami92 commented Sep 16, 2021

Please stop 🛑 the discussion about whether this feature is wanted or not.
This is expected valid JS embedded in Vue templates and the JS/ECMA-version supported for embedded code in Vue template is just "outdated" and therefore doesn't support the optional chaining syntax.

I'm nut sure if we want to support it in Vue 2... I understand this. But in Vue 3 this is an essential need. (if not already possible, I'm sadly a bit behind due to lacking support of Vuetify)

Edit: Seems Vue 3 already supports it

@cwilby
Copy link

cwilby commented Oct 6, 2021

One more way to do this, didn't want to use lodash.

Add a mixin / function

Vue.mixin({
  methods: {
    $get(obj, path, defaultValue = null) {
      let result = obj;
      for (let piece of path.split('.')) {
        result = result[piece];
        if (!result || typeof result !== 'object') {
          return result || defaultValue;
        }
      }
      return defaultValue;
    }
  }
});

Use in templates

<template>
  <p>{{ $get(company, 'representative.address.street', '(No address)') }}</p>
</template>

May not (probably does not) work for all scenarios, but helped me move on.

Later I'll search replace with \$get\((.*?), '(.*?)'\).

@maximilliangeorge
Copy link

maximilliangeorge commented Jan 27, 2022

Try vue-template-babel-compiler

It will enable Optional Chaining(?.), Nullish Coalescing(??) and many new ES syntax for Vue.js SFC based on Babel.

Github Repo: vue-template-babel-compiler

DEMO

DEMO

Usage

Please refer to REAMDE for detail usage

Support for Vue-CLI, Nuxt.js, Webpack, vue-jest , any environment use vue-loader.

This works, thanks a lot! The only reply that didn't suggest various hacks and workarounds.

@souljorje
Copy link

souljorje commented Jan 31, 2022

Let's support this issue to implement it in vite & vue 2 underfin/vite-plugin-vue2#161

@caocos
Copy link

caocos commented Apr 9, 2022

In the file node_modules\vue-template-babel-compiler\lib\index.js

184 line find errors edit errors:[] good job

@robert-niestroj
Copy link

robert-niestroj commented Jun 21, 2022

The blog post on Vue 2.7 https://blog.vuejs.org/posts/vue-2-7-naruto.html says:

2.7 also supports using ESNext syntax in template expressions.

Will this release solve this?

@DRoet
Copy link
Author

DRoet commented Jul 1, 2022

@robert-niestroj I tested this today and it seems to work in v2.7 (make sure you have vue-loader >=v15.10 installed)

@MartinX3
Copy link

MartinX3 commented Jul 1, 2022

Be aware that you can't use typescript there with Vue 2.7.
If you need it use Vue 3.x.

@robert-niestroj
Copy link

I tested it today with Quasar Framework 1.19.4, based on Vue 2.7.1 and can confirm it works.

@DRoet
Copy link
Author

DRoet commented Jul 6, 2022

I'm gunna go ahead and close this issue for now since there is a solid solution, upgrading to v2.7 should be pretty easy for most users.

@DRoet DRoet closed this as completed Jul 6, 2022
@maxkopych
Copy link

@DRoet updating vue to 2.7.10 didn't fix problem for me.
My packages:

    "vue": "2.7.10",
    "vue-loader": "15.10.0",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.10",

Did you configure something in webpack?

@Songoo7
Copy link

Songoo7 commented Oct 10, 2022

Sadly plugin above did not help at all for chaining. To update from 2.4 to 2.7 and webpack from 3 to 4 with babel to 7 seems like one month job alone on my project T.T .

I wish there would be some easy solution :( , like on vue 1 project it worked just like that with no changes needed :D .

For now using function above, tnx at least for that!

@MartinX3
Copy link

Sadly plugin above did not help at all for chaining. To update from 2.4 to 2.7 and webpack from 3 to 4 with babel to 7 seems like one month job alone on my project T.T .

I wish there would be some easy solution :( , like on vue 1 project it worked just like that with no changes needed :D .

For now using function above, tnx at least for that!

Just migrate to each minor version instead of doing a big jump.

@Songoo7
Copy link

Songoo7 commented Oct 11, 2022

Plugin did not work because it needs vue-loader 15, but jump from 14 to 15 has so many bcbreaks that after day I was lucky to even be able roll back to working solution :D .

For webpack 3 to 4 is much much worse, I have all minors at max, but when doing big jumps there are just too many bc-breaks, its old project and it show like hundreds of errors :) . At times like this, just miss vue 1, where chain work just like that :-) .

@TheJaredWilcurt
Copy link

TheJaredWilcurt commented Oct 12, 2022

Vue-CLI 4 + Vue 2.7

I am not able to get {{ foo?.bar?.baz }} or v-if="foo?.bar?.baz" to work.

Here is a simplified version of my deps:

"dependencies": {
  "vue": "2.7.11",
  "vue-router": "3.6.5",
  "vuex": "3.6.2"
},
"devDependencies": {
  "@babel/core": "^7.19.3",
  "@babel/eslint-parser": "^7.19.1",
  "@babel/runtime": "^7.19.4",
  "@vue/cli-plugin-babel": "^4.5.19",
  "@vue/cli-plugin-eslint": "^5.0.0-rc.2",
  "@vue/cli-plugin-router": "^4.5.19",
  "@vue/cli-plugin-vuex": "^4.5.19",
  "@vue/cli-service": "^4.5.19",
  "@vue/test-utils": "^1.3.0",
  "core-js": "^3.25.5",
  "eslint": "^8.25.0"
}

I removed vue-template-compiler because the docs said it is no longer needed with Vue 2.7.

What do I need to change to allow Optional Chaining in the template?

Tagging @DRoet because they closed this in hopes of an answer or re-opening. As this seems like a very common situation (Vue-CLI).

@maxkopych
Copy link

Vue-CLI 4 + Vue 2.7

I am not able to get {{ foo?.bar?.baz }} or v-if="foo?.bar?.baz" to work.

Here is a simplified version of my deps:

"dependencies": {
  "vue": "2.7.11",
  "vue-router": "3.6.5",
  "vuex": "3.6.2"
},
"devDependencies": {
  "@babel/core": "^7.19.3",
  "@babel/eslint-parser": "^7.19.1",
  "@babel/runtime": "^7.19.4",
  "@vue/cli-plugin-babel": "^4.5.19",
  "@vue/cli-plugin-eslint": "^5.0.0-rc.2",
  "@vue/cli-plugin-router": "^4.5.19",
  "@vue/cli-plugin-vuex": "^4.5.19",
  "@vue/cli-service": "^4.5.19",
  "@vue/test-utils": "^1.3.0",
  "core-js": "^3.25.5",
  "eslint": "^8.25.0"
}

I removed vue-template-compiler because the docs said it is no longer needed with Vue 2.7.

What do I need to change to allow Optional Chaining in the template?

Tagging @DRoet because they closed this in hopes of an answer or re-opening. As this seems like a very common situation (Vue-CLI).

I had similar problem. Fix for that was configure babel-loader in webpack for js files. Example for my rails app:

module.exports = {
    test: /\.(js)?(\.erb)?$/,
    exclude: /node_modules/,
    use: ['babel-loader']
};

@TheJaredWilcurt
Copy link

@maxkopych You showed code, but did not mention what file to put it in. To be clear, I am not using Webpack. I am using Vue-CLI (which in turn handles Webpack for me). So any Webpack tweaks need to be handled via the vue.config.js file, likely piggy-backing off of chainWebpack.

I'm asking if I need to change something there, and if so, what do I change?

@maxkopych
Copy link

maxkopych commented Oct 12, 2022

@maxkopych You showed code, but did not mention what file to put it in. To be clear, I am not using Webpack. I am using Vue-CLI (which in turn handles Webpack for me). So any Webpack tweaks need to be handled via the vue.config.js file, likely piggy-backing off of chainWebpack.

I'm asking if I need to change something there, and if so, what do I change?

I didn't work with vue-cli for awhile. But again if you gonna read this article
you will see line:
2.7 also supports using ESNext syntax in template expressions. When using a build system, the compiled template render function will go through the same loaders / plugins configured for normal JavaScript. This means if you have configured Babel for .js files, it will also apply to the expressions in your SFC templates.

All you need is search how to add new rules to loaders in vue-cli. first that I found: https://stackoverflow.com/questions/59960121/how-can-i-add-new-rules-to-loaders-while-using-vue-cli-3-x

@DRoet
Copy link
Author

DRoet commented Oct 12, 2022

@TheJaredWilcurt are you using vue-loader: ^v15.10.0? might want to check your lockfile to check if it is pulling in the right version

@TheJaredWilcurt
Copy link

Here is my package-lock.json, slimmed down to just the parts relating to vue-loader:

{
  "packages": {
    "node_modules/@vue/cli-service": {
      "version": "4.5.19",
      "dependencies": {
        "vue-loader": "^15.9.2"
      },
      "optionalDependencies": {
        "vue-loader-v16": "npm:vue-loader@^16.1.0"
      }
    },
    "node_modules/@vue/cli-service/node_modules/vue-loader-v16": {
      "name": "vue-loader",
      "version": "16.8.3"
    },
    "node_modules/vue-loader": {
      "version": "15.9.8"
    }
  },
  "dependencies": {
    "@vue/cli-service": {
      "version": "4.5.19",
      "requires": {
        "vue-loader": "^15.9.2",
        "vue-loader-v16": "npm:vue-loader@^16.1.0"
      },
      "dependencies": {
        "vue-loader-v16": {
          "version": "npm:[email protected]"
        }
      }
    },
    "vue-loader": {
      "version": "15.9.8"
    }
  }
}

@amc1999
Copy link

amc1999 commented Dec 7, 2022

I had same problem and manage to solve it by adding babel plugins:

// babel.config.js
module.exports = {
  plugins: [
    '@babel/plugin-proposal-nullish-coalescing-operator',
    '@babel/plugin-proposal-optional-chaining'
  ],
  presets: [
    .....did not change anything in presets....

Just for the info, versions from package-lock (as it is today) are:

"quasar": {
      "version": "1.22.2",
"vue": {
      "version": "2.7.14",
 "vue-loader": {
      "version": "15.10.1",

As you can see we are still on Quasar 1 and Vue 2, planning move to Vue 3, but not now.

@richard-d-vindico
Copy link

Can I just check, this wasn't working in the template before (only in script logic), but now it works in the template just fine.

Did something change (i'm using v2.6.11)?

@cwilby
Copy link

cwilby commented Jan 19, 2023

@richard-d-vindico Vue 2.7 added support for using ESNext syntax in template expressions. (https://blog.vuejs.org/posts/vue-2-7-naruto.html)

Run npm ls | grep vue@ to see what your specific version of Vue is.

@richard-d-vindico
Copy link

richard-d-vindico commented Jan 19, 2023 via email

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

No branches or pull requests