Support optional chaining in vuejs
Asked Answered
C

9

30

I have created vue and electron app using @vue/cli-service 4.2 in that I am facing a issue of optional chaining.

I can't use ? for validating the condition like (@babel/plugin-proposal-optional-chaining)

eg. a?.b?.c its means it check weather a exist then check for b otherwise return false same as template expression in angular.

Any one have idea how to configure optional chaining in vuejs.

Chancechancel answered 13/5, 2020 at 9:26 Comment(2)
Are you talking about supporting optional chaining even in browsers that don't support it? Or does optional chaining fail in Vue for some reason, even if the browser does support it?Incoming
@Flimm: Not for browser. iI am taking about the optional chaining in vuejs only.In react and angular we have optional chaining support but in vue js there has no suport.Chancechancel
W
13

One quick update is that Vue 3 comes bundled with support for optional chaining.

To test you can try compiling the below Vue component code.

<template>
  <div id="app" v-if="user?.username">
    @{{ user?.username }} - {{ fullName }} <strong>Followers: </strong>
    {{ followers }}
    <button style="align-self: center" @click="followUser">Follow</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'App',
  props: {
    test: Object
  },
  data() {
    return {
      followers: 0,
      user: {
        id: 1,
        test: {},
        username: '_sethAkash',
        firstName: undefined,
        lastName: 'Seth',
        email: '[email protected]',
        isAdmin: true
      }
    }
  },
  computed: {
    fullName(): string {
      //
      return `${this?.test?.firstName} ${this?.user?.lastName}`
    }
  },
  methods: {
    followUser: function () {
      this.followers += 1
    }
  },
  watch: {
    followers(newFollowerCount, oldFollowerCount) {
      if (oldFollowerCount < newFollowerCount) {
        console.log(`${this?.user?.username} has gained a follower!`)
      }
    }
  },
  mounted() {
    this.followUser()
  }
})
</script>
Wirephoto answered 26/11, 2020 at 18:23 Comment(0)
P
11

Try vue-template-babel-compiler

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

Github Repo: vue-template-babel-compiler

DEMO

DEMO Image

Usage

1. Install

npm install vue-template-babel-compiler --save-dev

2. Config

1. Vue-CLI

DEMO project for Vue-CLI

// vue.config.js
module.exports = {
    chainWebpack: config => {
        config.module
            .rule('vue')
            .use('vue-loader')
            .tap(options => {
                options.compiler = require('vue-template-babel-compiler')
                return options
            })
    }
}

2. Nuxt.js

DEMO project for Nuxt.js

// nuxt.config.js
export default {
  // Build Configuration: https://go.nuxtjs.dev/config-build
  build: {
    loaders: {
      vue: {
        compiler: require('vue-template-babel-compiler')
      }
    },
  },
  // ...
}

Please refer to REAMDE for detail usage

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

Peppie answered 1/8, 2021 at 11:33 Comment(7)
webpack configs didn't worked for me... is there any requirements? I have vue-loader 14 and webpack 3 that outdated, everything else more or less up to dateEnamor
@MaxymKopych , Sorry, It is not compatible with vue-loader <= 15.0.0. This may be a chance for you to upgrade your project.Peppie
@JuniorTour I have vue-loader 15.9.6 and it still not working.Jaimie
@LocTruong Do you got any error? What is your Node.js version? This lib currently only support for Node.js v14+. You can also create a bug issue to describe your problem more specifically. So that we can help you fix it ASAP.Peppie
Hi, do you know if it breaks anything else (like v-for destructuring)?Chardin
This lib will NOT break anything exist, it compatible with all Vue.js syntax. If you run across any error, just create a GitHub issue, it is still under maintenance, error will be fixed.Peppie
After updating my node to v16 it started not working. Any changes I need to do to support that @JuniorTourStripy
A
8

According to this comment on an issue here

You could create a global mixin and use the eval function to evaluate the expression.

Example:

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

In the template:

<template>
  <p>{{ $evaluate('user?.name') }}</p>
</template>

They also added that it might not be perfect:

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


Edit

As stated above, using eval may bring some unintended problems, I suggest you use a computed property instead.

In the SFC:

<template>
  <p>{{ userName }}</p>
</template>

<script>
export default {
  data(){
    return { 
      user: {
        firstName: 'Bran'
      }
    }
  },
  computed: {
    userName(){
      return this.user?.firstName
    }
  }
}
</script>
Arnhem answered 17/5, 2020 at 18:54 Comment(1)
You shouldn't use eval. It causes errors in some browsers and obviously it's a bad practice.Obstipation
B
1

This doesn't work exactly the same but I think, in this context, it may be event better for most cases.

I used Proxy for the magic method effect. You just need to call the nullsafe method of an object and from there on, just use normal chaining.

In some versions of VueJs you can't specify a default value. It perceives our null safe value as an object (for good reason) and JSON.stringify it, bypassing the toString method. I could override toJSON method but you can't return the string output. It still encodes your return value to JSON. So you end up with your string in quotes.

const isProxy = Symbol("isProxy");
Object.defineProperty(Object.prototype, 'nullsafe', {
  enumarable: false,
  writable: false,
  value: function(defaultValue, maxDepth = 100) {
    let treat = function(unsafe, depth = 0) {
      if (depth > maxDepth || (unsafe && unsafe.isProxy)) {
        return unsafe;
      }
      let isNullish = unsafe === null || unsafe === undefined;
      let isObject = typeof unsafe === "object";
      let handler = {
        get: function(target, prop) {
          if (prop === "valueOf") {
            return target[prop];
          } else if (typeof prop === "symbol") {
            return prop === isProxy ? true : target[prop];
          } else {
            return treat(target[prop], depth + 1);
          }
        }
      };
      let stringify = function() {
        return defaultValue || '';
      };
      let dummy = {
        toString: stringify,
        includes: function() {
          return false;
        },
        indexOf: function() {
          return -1;
        },
        valueOf: function() {
          return unsafe;
        }
      };

      return (isNullish || isObject) ? (new Proxy(unsafe || dummy, handler)) : unsafe;
    };

    return treat(this);
  }
});


new Vue({
  el: '#app',
  data: {
    yoMama: {
      a: 1
    }.nullsafe('xx'),
    yoyoMa: {
      b: 1
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  {{ yoMama.yoyoMa.yoMama.yoyoMa.yoMama }}
  <hr> {{ yoyoMa.nullsafe('yy').yoMama.yoyoMa.yoMama.yoyoMa }}
</div>
Ballast answered 28/5, 2021 at 17:9 Comment(0)
H
1
/*
 * 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>
Headrace answered 28/6, 2021 at 21:28 Comment(0)
L
0

After search many possibilities, I maked one function to help me.

Make one js file to save the helper function and export it

const propCheck = function (obj = {}, properties = ""){

    const levels = properties.split(".");
    let objProperty = Object.assign({}, obj);

    for ( let level of levels){
        objProperty =  objProperty[level];
        if(!objProperty) 
            return false;
    }

   return true;

}
export default propCheck;

And install this function for globally in the Vue instance

Vue.prototype.$propCheck = propCheck;

After use in your template

<span>{{$propCheck(person, "name")}}</span>

or

<span>{{$propCheck(person, "contatcs.0.address")}}</span>

or

<span>{{$propCheck(person, "addres.street")}}</span>
Laicize answered 24/5, 2021 at 14:17 Comment(1)
I think that in the Vue3 the package vue-loader will support the operator "optional chain", and it will no longer be necessary this functionLaicize
S
0

Use getSafe() method way for template and js files :)

<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>
Skyler answered 18/5, 2022 at 12:7 Comment(0)
E
0

July 2022 Update: It works with Vue 2.7 (https://blog.vuejs.org/posts/vue-2-7-naruto.html)

2.7 also supports using ESNext syntax in template expressions.

Euclid answered 6/7, 2022 at 10:47 Comment(0)
W
0

You can use loadash's get method in this case:

_.get(object, path, [defaultValue])

Gets the value at path of object. If the resolved value is undefined, the defaultValue is returned in its place.

https://lodash.com/docs/4.17.15#get

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// => 3

_.get(object, 'a.b.c', 'default');
// => 'default'
Woodpile answered 24/8, 2022 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.