Marc Pont
Marc Pont

Reputation: 1068

Vuejs can't use lodash inside template but it works on code

I have lodash imported globally with:

window._ = require('lodash'); // app.js

and it works fine when I use it on the code like in methods. But when I try to use it inside templates like:

{{_.get(user, 'address.name')}} 

shows undefined error:

Property or method "_" is not defined on the instance but referenced during render

Why happen this? I could refactor creating a new variable and assign the value in code and it will work, but I want to use it directly on template too.

Upvotes: 0

Views: 1626

Answers (3)

4uroraskye
4uroraskye

Reputation: 440

For those who prefer lodash to be available globally in all files, and/or use lodash.mixins.

I decided to use the app.provide and inject it into components where I wanna use lodash in the template. The only problem I has was that I couldn't use _, I had to settle on $_.


file structure

.
├── src
│   ├── components
│   │   └── BaseBtn.vue
│   └── utils
│       ├── global
│       │   └── plugins.js
│       └── plugins
│           └── lodash.js
└── main.js

main.js

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import '@/utils/plugins/lodash'
import App from './App.vue'
import router from './router'
import registerGlobalPlugins from '@/utils/global/plugins'

import './assets/scss/index.scss'

const app = createApp(App)

app.use(createPinia())
app.use(router)
registerGlobalPlugins(app)

app.mount('#app')

src/utils/plugins/lodash.js

import _ from 'lodash'

export const lodashMixin = {
  /** Converts obj keys to camelCase */
  $camelCaseKeys (object) {
    return _.mapKeys(object, (_value, key) => _.camelCase(key))
  },

  /** Pauses code for x milliseconds */
  $pause (milliseconds) {
    return new Promise((resolve) => setTimeout(resolve, milliseconds))
  },
}

_.mixin(lodashMixin)
window._ = _

export const lodashPlugin = {
  install (app) {
    app.provide('$_', _)
  },
}

src/components/BaseButton.vue

<template>
  <button>
    {{ $_.sample(['this', 'is', 'an', 'example']) }}
  </button>
</template>

<script>
export default {
  inject: ['$_'],

  methods: {
    async handleClick () {
      await _.$pause(500) // this will work due to import in main.js
      await this.$_.$pause(500) // this should also work via the inject
    }
  }
}
</script>

Upvotes: 0

Terry
Terry

Reputation: 66103

Extending on my comment: I usually discourage using third party util/helper methods inside VueJS template. This is, of course, a personal choice, but it is way simpler to let VueJS handle the rendering directly (and also guards against possible reactivity issues down the road). Therefore, you can simply use a computed property (or a method, if you need to pass arguments) to generate the string, which is inserted into the template.

Example:

computed: {
  addressName() {
    return _.get(this.user, 'address.name');
  }
}

Then, in your template, you can simply use {{ addressName }} to render the string. Should you require more dynamic use with more flexibility and abstraction, you can use methods instead. For example, if your path is going to be dynamic, you can then create a method that retrieves data from this.user with a provided path argument:

methods: {
  userData(path) {
    return _.get(this.user, path);
  }
}

In your template you can simply use {{ userData('address.name') }}.

Upvotes: 2

Victor Castro
Victor Castro

Reputation: 1242

That's probably because the rendering (so the call of the property "_") is done before his instantiation.

In your case, you may have to set window._ in the created lifecycle callback that is called before the rendering.

But my recommendation is to set this in a "data" property of your component and even to only import and set the functions you need.

For exemple:

import clone from 'lodash/clone'

Upvotes: 1

Related Questions