Hardik Kothari
Hardik Kothari

Reputation: 1766

Support optional chaining in vuejs

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.

Upvotes: 31

Views: 36915

Answers (9)

Abraham Izadi
Abraham Izadi

Reputation: 51

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'

Upvotes: 0

Robert Niestroj
Robert Niestroj

Reputation: 16131

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.

Upvotes: 0

Hamid Ali
Hamid Ali

Reputation: 1

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>

Upvotes: 0

toraman
toraman

Reputation: 604

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>

Upvotes: 1

Junior Tour
Junior Tour

Reputation: 640

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+.

Upvotes: 11

primegxy
primegxy

Reputation: 1858

/*
 * 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>

Upvotes: 1

Alison Rodrigues
Alison Rodrigues

Reputation: 41

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>

Upvotes: 0

Akash Kumar Seth
Akash Kumar Seth

Reputation: 1701

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>

Upvotes: 14

Edmund1645
Edmund1645

Reputation: 349

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>

Upvotes: 8

Related Questions