Including libraries in components through computed properties - javascript

When I try to call libraries such as lodash or momentjs inside the {{ mustache syntax }} in Vue template files I keep getting the 'undefined property' error, even though they are bound to the window object.
To go around this, I am importing the libraries into my components, and then returning the objects in computed properties, as in:
import _ from 'lodash';
export default {
computed: {
_() { return _; }
}
}
Is it a good way to handle this issue? What would be the recommended approach? Are there any downsides to handling it through computed properties instead of methods?

Basically, yes, creating a computed property is the way to go.
But you have some alternatives to make that simpler.
A Global Mixin is one good alternative that requires little code:
Vue.mixin({
computed: {
window() { return window; }
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ window.location.host }}</p>
</div>
This way the window computed property will be available to (and thus usable in the template of) any Vue component.

I would recommend to move all logic to compute property. This makes your code cleaner and more readable.
Think of your component as MVC, where templates are View, the component object is a controller and properties and computed properties are model. You don't put logic in the view layer, right?
So instead of
<template>
<div>
{{ _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x') }}
</div>
</template>
export default {
computed: {
_() { return _; }
}
}
something like this:
<template>
<div>
{{ diff1 }}
</div>
</template>
export default {
computed: {
diff1: _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'),
}
}

Related

How to display the getter as text in the template

I have a getter, which I would like to display in the template as text.
The data is already in the getter, checked with devtools vue chrome extension.
The data I want to get from the getter looks like this:
["appel", "banaan", "kiwi"]
My component looks like this: (the name of getter is getPlaylist)
<template>
<div>
<span>{{ showPlaylist }}</span>
</div>
<template>
<script>
import { mapGetters } from "vuex";
export default {
data() {
return {
};
},
computed: {
...mapGetters("app"["getPlaylist"]),
showPlaylist() {
this.getPlaylist;
},
},
};
</script>
How can I use this getter to show the text of the array?
Thanks in advance!
Do you want to display this ["appel", "banaan", "kiwi"] in the template
If yes, modify the code to
computed: {
...mapGetters("app"["getPlaylist"]),
showPlaylist() {
return this.getPlaylist;
},
}
Not sure what you're hoping to get with ...mapGetters("app"["getPlaylist"]), but I can't see it doing anything else other than being an undefined
"app" is a string, which doesn't behave like an object or array. the only thing available that could be accessed is string methods (ie "app"["startsWith"]("a")) or characters "app"[2])
if app is a module and getPlaylist the getter, you need to define it as:
computed: {
...mapGetters(["app/getPlaylist"]),
}
alternatively if app is another getter...
computed: {
...mapGetters(["app","getPlaylist"]),
}
or if app is a namespace
computed: {
...mapGetters("app",["getPlaylist"]),
}

Vue component not being rendered with props [duplicate]

This question already has an answer here:
Vue basics -- use component in other component
(1 answer)
Closed 2 years ago.
I am beginner in Vue. I created a custom component and tried to bind everything exactly as shown in the starter Vue cli template. Here is the code.
Circle.vue
<template>
<div :style="custom">
</div>
</template>
<script>
export default {
name:'Circle',
props:{
size:String,
color:String
},
computed:{
custom(){
return {
background:this.color,
height:this.size,
width:this.size
}
}
}
}
</script>
inside my View.vue file
<script>
// :class="['']"
import Circle from '#/components/Circle.vue'
export default {
name: "Landing",
components:{
Circle
}
};
</script>
I tried to use it so
<Circle size="100px" color="#222222"/>
I tried printing the props as it is but it also doesnt work
<template>
<div :style="custom">
{{size}} {{color}}
</div>
</template>
Nothing is being shown on the screen after I do this. I took help from here
Thanks for your time!
As mentioned in the docs:
Component names should always be multi-word, except for root App components, and built-in components provided by Vue, such as <transition> or <component>.
This prevents conflicts with existing and future HTML elements, since all HTML elements are a single word.
You have two options when defining component names:
With kebab-case
Vue.component('my-circle', { /* ... */ })
When defining a component with kebab-case, you must also use kebab-case when referencing its custom element, such as in <my-circle>.
With PascalCase
Vue.component('MyCircle', { /* ... */ })
When defining a component with PascalCase, you can use either case when referencing its custom element. That means both <my-circle> and <MyCircle> are acceptable.
Demo:
Vue.component('my-circle', {
props: {
size: String,
color: String
},
template: '<div :style="custom"></div>',
computed: {
custom() {
return {
background: this.color,
height: this.size,
width: this.size
}
}
}
})
new Vue({
el: "#myApp"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
<my-circle size="100px" color="#222222" />
</div>

Wait for definition before calling function - Vue.js

When it comes to creating methods in child components I'm having a hard time figuring a particular feature out.
I have this parent route/component (League.vue):
In this league.vue I render a child component:
<router-view :league="league" />
Child component:
<template>
<div v-if="teams_present">
<div class="page-container__table">
<h3 class="page-container__table__header">Teams</h3>
</div>
</div>
</template>
<script>
export default {
name: 'LeagueTeams',
props: [
'league'
],
data () {
},
computed: {
teams_present: function () {
return this.league.teams.length > 0
}
}
}
</script>
ERROR:
"TypeError: Cannot read property 'length' of undefined"
So it appears that the computed callback is called before the prop can be set, I think? and if a change it to methods it never gets called. How do I handle this case?
As Ali suggested, you can return this.league.teams && this.league.teams.length > 0, which definitely will work.
However, as my experience, to avoid these situation, and for good practice, always declare the type of the Props. So in your props:
export default {
name: 'LeagueTeams',
props: {
league: {
type: Object, // type validation Object
default() { return {teams: [] }} // add a default empty state for team, you can add more
}
},
data () {
},
computed: {
teams_present: function () {
return this.league.teams.length > 0 // now the old code should work
}
}
}
</script>
By doing this, you don't need to care much about checking the edge case of this.league.teams every time, since you may need to call it again in methods or in the <template> html
Update: Another suggestion is if you are using vue-cli 4, you can use Optional chaining and nullish coalescing.
return this.league?.teams.length ?? false // replace with only this line will work
Hope this will help you 2 more ways to deal with in these situations, and depends on situations you can choose the most suitable one

vue.js component inline style concatenation

I'm stuck with a vue.js component inline style concatenation.
My code is the following:
components: {
'twitter-item': {
props: ['procolors'],
template: '\
<div class="color-quadrat" v-bind:data-id="procolors.id" v-bind:style="background-color: #{procolors.user.profile_background_color}">\
<p>{{procolors.user.profile_background_color}}</p>\
</div>\
'
}
}
I'm trying to get procolors.user.profile_background_color as inline background color. Special is that the value from procolors.user.profile_background_color has no #. So I have to add this in the template.
I tried all kinds of recommendations from the web, but none worked for me.
Any help appreciated!
Use this, which utilizes vue's style object syntax:
:style="{backgroundColor: '#' + procolors.user.profile_background_color}"
You have several choices in how to add styling. If you use v-bind:style="...", or it shorthand :style="...", you need to pass either a valid string, valid variable or a valid object.
Currently you are trying to parse background-color: #{procolors.user.profile_background_color} as javascript, which is not going to work.
You can use a javascript template to create a string:
components: {
'twitter-item': {
props: ['procolors'],
template: '\
<div class="color-quadrat" v-bind:data-id="procolors.id" v-bind:style="`background-color: #${procolors.user.profile_background_color}`">\
<p>{{procolors.user.profile_background_color}}</p>\
</div>\
'
}
}
It is often more readable to refactor it to use a variable or function instead:
components: {
'twitter-item': {
props: ['procolors'],
template: '\
<div class="color-quadrat" v-bind:data-id="procolors.id" v-bind:style="rowColor">\
<p>{{procolors.user.profile_background_color}}</p>\
</div>\
',
computed: {
rowColor () {
return {
"background-color": `#${this.procolors.user.profile_background_color}`
}
}
}
}
}
Accoding to Binding inline styles documentation there are to ways to pass inline styles - as an object or as an array.
In your example, background-color: #{procolors.user.profile_background_color} is neither object or an array.
For sake of readability and maintainability (and good practice in general), I'd suggest to create a computed property that will return an object with inline styles. This way it will more clear where is the issue with concatenation:
Template will look as follows:
<div
class="color-quadrat"
:data-id="procolors.id"
:style="itemStyles">
<p>{{ procolors.user.profile_background_color }}</p>
</div>
And computed property should be added to the same component:
props: ['procolors'],
template: `...`,
computed: {
itemStyles () {
return {
backgroundColor: `#${this.procolors.user.profile_background_color}`
}
}
}
If you still prefer to keep it inline, then style binding should be changed to following:
v-bind:style="{ backgroundColor: `#${procolors.user.profile_background_color}` }"
For those who want to use style binding with vue3. This is the solution:
<script setup lang="ts">
import {ref} from 'vue'
const color = ref("red")
</script>
<template>
<div class="parti-color"
:style="{backgroundColor: color, width: '20px', height: '30px'}"
/>
</template>

How to share "computed" methods across a Vue.js application

I have a Vue.js application which loads a list of items, and each item is passed as a prop to a Vue component.
I figured out that by using mixins I can share common component properties, like computed,created, etc.
Now, I'm trying to sort the list of items and can't figure out how I would access each component's computed properties to apply sorting/filtering. How can I accomplish this?
Items
[{
price: 10,
qty: 2
}, {
price: 8,
qty: 3
}]
Mixin - ./Cost.js
export default {
computed: {
cost () {
return this.price * this.qty;
}
}
}
Component (which works as expected) - ./Product.vue
import Cost from './Cost.js'
export default {
name: 'product-item',
props: ['product'],
mixins: [Cost]
}
How would you access the computed properties, or restructure this setup?
List component
<template>
<div id="list">
<div v-for="product in sorted" :product="product">Cost: {{ cost }} </div>
</div>
</template>
<script>
import ProductItem from './Product.vue'
export default {
components: { ProductItem },
created: () {
this.items = [...] // as noted above
},
computed: {
sorted () {
return this.items.sort( (a,b) => b.cost - a.cost); // cost is not accessible!
}
}
}
</script>
Use vuex. Your vuex store will provide a getters object that can be wrapped into multiple components’ native computed objects, or accessed directly. Your code will be DRY, reactive, cached, and maintainable.
From my experience, once you need to go beyond child-parent data relationships, vuex, store, and shared state are the way to go. Once you get the hang of it, it is downright magical how your app evolves.
It is beyond scope of the question to show how to install vuex. Visit https://vuex.vuejs.org/guide/getters.html to see how getters are similar to computed properties, with the value of being shared between components. The official Vuex guide will also demonstrate how to initialize your Vue instance with the store.
Here are some snippets to show you the actors in the vuex system.
Store and State
// state definition (basically a shared reactive 'data' object that lives outside components)
state:{
message:'Hello'
}
// the store getters are declared as methods and accessed as properties (just like component/computed)
getters:{
message: state => return state.message
}
Accessing From Components
// component 1 wraps getter
computed:{
message(){
return this.$store.getters.message
}
}
// component 2 also wraps getter
computed:{
message(){
return this.$store.getters.message
}
}
// templates can also use getters directly
<div>{{$store.getters.message}}</div>
// If message was wrapped, you can simply use the computed property
<div>{{message}}</div>
Once you start using vuex, all sorts of other treasures start to emerge, such as the developer tools in Chrome, undo/redo support, simple refactoring of state, time-travel debugging, app persistence, etc. There are also shortcuts for adding multiple store getters into your computed properties.
As suggested by #Sphinx, you could use a ref to access the child component.
For example:
<template>
<div id="list">
<product-item v-for="product in sorted" :product="product" :ref="product"></product-item>
</div>
</template>
<script>
import ProductItem from './Product.vue'
export default {
components: { ProductItem },
data: () => ({
hidrated: false,
items: []
})
created() {
this.items = [...] // as noted above
},
mounted() {
this.hidrated = true
},
computed: {
sorted () {
if (!this.hidrated && !Object.keys(this.$refs).length) {
// handle initial state, before rendered
return this.items
}
return Object.values(this.$refs)[0]
.sort((a,b) => b.cost - a.cost)
.map(c => c.product)
}
}
}
</script>
This is assuming you have no other ref in your List Component.
You also have to check if the component is rendered first, here I use hidrated to flag when the component is mounted.

Categories