vue: Optional chaining in templates does not seem to work

Version

15.8.3

Reproduction link

https://template-explorer.vuejs.org/#<div id%3D"app" v-if%3D"obj%3F.a">{{ msg }}<%2Fdiv>

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"

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 276
  • Comments: 66 (6 by maintainers)

Most upvoted comments

Potential workaround includes using lodash’s get as stated in https://github.com/vuejs/vue/issues/4638#issuecomment-397770996

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

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 https://github.com/vuejs/vue-loader/commit/4cb447426ec19c4e07f22ce73fe134f8abaf007c#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.

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.

Optional chaining for Vue2 please!

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')" />

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.

You can write a method that returns its value. example in under

template :

image

methods :

image

Checking property presence hardly constitutes as application logic. Having the function as a workaround is one thing, but stating that it is the preferred approach for the sake of clean code is a bit absurd.

mark. Have the same problem with using ?. in template 2.x. Wait for support

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.

It is not correct to use the ?. syntax. The Vue design is to view the data binding on the data already processed by Reactive. It is a stable data model.

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.

/*
 * Where to use: Use in vue templates to determine deeply nested undefined/null values
 * How to use: Instead of writing parent?.child?.child2 you can write
 *            isAvailable(parent, 'child.child2')
 * @author    Smit Patel
 * @params    {Object} parent
 *            {String} child
 * @return    {Boolean}     True if all the nested properties exist
 */
export default function isAvailable(parent, child) {
  try {
    const childArray = String(child).split('.');
    let evaluted = parent;
    childArray.forEach((x) => {
      evaluted = evaluted[x];
    });
    return !!evaluted;
  } catch {
    return false;
  }
}

Use :

<template>
  <div>
    <span :v-if="isAvailable(data, 'user.group.name')">
      {{ data.user.group.name }}
    <span/>
  </div>
</template>
<script>
import isAvailable from 'file/path';
export default {
   methods: { isAvailable }
}
</script>

@adjenks I know that when you write a function, the amount of code written increases but each increase not bad .

why :

  1. because your application logic is inside script not presentation layer
  2. because you can test it in the test unit.
  3. because according to the Clean Code Book , your application logic is inside a wrapper ( function ) , This helps to read the code and the others will understand the reason for this.

This would definitely be great to have in Vue2! Will there be support added?

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?

optional chaining support for Vue2 please! If I were to take a jab at this, is this still the right path?

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.

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

@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?

But from the perspective of JavaScript grammar, such support should be provided. What is executed is an expression and does not depend on the data model. 😂

image

Here’s a codesandbox where it works fine: https://codesandbox.io/s/vigilant-bash-bjjvl?file=/src/App.vue

private s<T>(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.

ah derp, totally read over that comment

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\((.*?), '(.*?)'\).

I’m on Vue 3.0.0, and it still doesn’t work. Did it work on a later version?

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.

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

@mohamadrezahedayati Clean Code isn’t just a book of hard and fast rules. By your logic, Vue components in themselves violate SRP.

@calevio is 100% correct.

Same behaviour for me on the latest Chrome, loader 16, and vue@latest

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')" />

@Sceat It’s not work.I fix it.

const optional_chain = (...parameters) => {
  const [source, ...properties] = parameters
  return properties.reduce((object, property) => object?.[property] ?? undefined, source)
}
const a={b:{c:{d:1}}}
console.log(optional_chain(a,'b','c','d'))

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.