How do I call a mixin function from another component? - javascript

MyMixin.vue has the method beginEdit.
What I'm trying to do is to make onFirstLineClick to call myMixin's beginEdit depending on the value of this.folded.
When I console logged myMixin.beginEdit, it is undefined and not surprisingly myMixin.beginEdit() doesn't work.
Am I missing something needed to use the function? If so, why does beginEdit work perfectly on <span>?
<template>
<div>
<div>
<div
#click="onFirstLineClick"
/>
<span
#click="beginEdit"
/>
</div>
</template>
<script>
import myMixin from './MyMixin';
export default {
name: 'currComponent',
mixins: [myMixin],
data() {
return {
folded: false,
};
},
methods: {
onFirstLineClick(e) {
// myMixin.beginEdit() doesn't work
}
},
};
</script>

The great thing about mixin is that when a component uses a mixin, all options in the mixin will be "mixed" into the component's own options. Which means that inside your component you can call mixin method directly like:
methods: {
onFirstLineClick(e) {
this.beginEdit()
}
},
That is also the reason why you can use beginEdit() method on <span> directly like:
<span #click="beginEdit" />
Please also know that in future if you declare a method in this component with same name as mixin method name beginEdit, then the component's method will take priority and you might see different behaviour. So, make sure to give unique names to mixin methods.

Related

Why Vue treats unspecified boolean props as false?

Please see this example
If I have a component like this:
<template>
<div v-if="on">
Hello
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
on: Boolean
},
mounted() {
console.log(this.on)
}
};
</script>
And then I render that component and specify nothing on the prop
<template>
<div >
<HelloWorld/>
</div>
</template>
This will output false instead of undefined?
Why is that?
Is there anyway I can detect if people didn't specify the prop, so I can use lodash _.isBoolean to detect it?
Currently, the only way I know is manually set the default to undefined.
You could set a default value on it and check if the value is still the same then the user didn't pass a prop, but there are instance where you can't really compare your value to its default, like if the prop is a function.
You could have this.$options.propsData inside of your component. If the prop is present here, the user has explicitly set it; default values aren't shown in.

this.$refs always empty or undefined in Vue component

I am new to Vue and am trying to use $refs to grab some elements in the DOM from a sibling component (for very basic purposes, just to get their heights, etc.), and I'm doing so in a computed.
No matter what I try, this.$root.$refs either always comes back undefined or as an empty object, and I don't know what I'm doing wrong.
In a parent component, I have:
<template>
<ComponentA />
<ComponentB />
</template>
In component A I have:
<template>
<div id="user-nav">
<div ref="nav-container">
<slot />
</div>
</div>
</template>
I just try to see if I can access this in ComponentB by console logging
console.log(this.$root.$refs);
in that component's mounted function.
But I keep getting an empty object.
Can you just not access things across sibling components like this???
You might have already solved this, but just to answer this Q:
I had a similar problem and it seems that this happens because the function is being called before Vue has had time to replace the root DOM with its own version. What you can do to fix this is to create a mounted life-cycle hook and call the function there.
const vm = new Vue({
el: '#app',
data: {
sayHi: 'Hello World',
},
mounted: function() { // The hook. Here, Vue has already worked its magic.
console.log('YOUR ELEMENT ----> ', this.doSomething())
},
methods: {
doSomething: function() {
return this.$refs['nav-container']
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<span ref="nav-container">{{ sayHi }}</span>
</div>
I found the solution here.
I had the same problem. I was using this.$refs in computed property. I just got rid of computed properties and used methods and everything started to work properly because $refs are not reactive as mentioned for example here and here
This might not be an answer to your particular problem but I discovered that another reason for $refs being empty is that the component on which it is defined has not created/mounted yet. Probably due to the lazy rendering Vue uses.
I had a set of tabs, one of which was the one I had a "ref=" in, and if I did not visit that tab then $refs was empty. But íf I first visited the tab then the ref was set up correctly.
You can do it, the problem you have is that you don't actually have any refs on your parent component.
I do not recommend doing this anyway either use vuex, an eventbus or $emit
Vue.component('componentA', {
template: '<div><span ref="ref-inside-a">You\'re in A!</span></div>',
methods:{
log(){
console.log('Hello from a')
}
}
})
Vue.component('componentB', {
props: ['ball'],
template: '<div><span>This is B!</span></div>',
mounted() {
this.$root.$refs['a'].log()
console.log(this.$root.$refs['a'].$refs['ref-inside-a'])
}
})
new Vue({
el: "#app",
mounted() {
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a ref="a"></component-a>
<component-b ref="b"></component-b>
</div>
I had the same problem but the cause was another one:
I had a v-if condition on the referenced element. So at the time of mounting the element was not rendered because the condition was still false.
After 3 hours of looking for the cause for the problem, it was so obvious.
<template>
<sub-component v-if="showCondition" ref="componentName">
<div>someContent</div>
<div>someMoreContent</div>
</sub-component>
</template>
<script lang="ts">
export default class MyComponent extends Vue {
private showCondition = false; // set to true after a REST request
mounted() {
console.log(this.$refs); // this was empty
}
}
</script>
This problem can be solved by moving the v-if to an additional template element inside the referenced component.
<template>
<sub-component ref="componentName">
<template v-if="showCondition">
<div>someContent</div>
<div>someMoreContent</div>
</template>
</sub-component>
</template>
<script lang="ts">
export default class MyComponent extends Vue {
mounted() {
console.log(this.$refs); // now 'componentName' is available
}
}
</script>
In my specific case the
this.$refs
part referenced to several components. Accessing a specific component by reference was possible:
this.$refs[componentName]
To access a specific field of the component above I had to select the first observable:
this.$refs[componentName][0].someFieldToBeSelected -> this works
this.$refs[componentName].someFieldToBeSelected -> this fails
.

Vue.js dynamic component with dynamic data

I'm trying to use the <component> element to dynamically display a chosen component. Each of these displayed components can take one of any number of data objects. Something like:
<div id="containers">
<component v-bind:is="currentView"></component>
</div>
var myVue = new Vue({
el:"#containers",
data:{
currentView: "my-component-one",
currentData: {...}
},
method: {
changeView: function(){
//change this.currentView
//change this.currentData
}
}
});
However, the Vue documentation says the v-bind:is attribute can be used to pass either a component name or the options object.
It is unclear how I would conditionally get an object of values for that component to use and also conditionally change which component is shown.
I am very green with using Vue (coming fresh off a knockout kick) so perhaps I am simply misunderstanding the intention of the component tag.
you can simply use v-bind
html
<component v-bind:is="currentView" v-bind="data"></component>
script
data()
{
return {
data: {
currentData: "example"
}
}
}
and it will pass currentData down to child. You can also add other properties along with it, including is.
If you need to change the component along with props, then you just change the data property, or whatever you want to call it.
https://codesandbox.io/s/7w81o10mlx
This example might help you understand it. https://jsfiddle.net/jacobgoh101/mLbrj5gd/
For passing Component Name
If the component is global, you can pass the component name to v-bind:is
for e.g.,
Vue.component('test1', {
template: `<div>test1</div>`
})
HTML
<component is="test1"></component>
For passing option
A Vue component is literally just a Javascript object with specific properties
for e.g.,
<component v-bind:is="{
template: `<div>test2</div>`
}"></component>

Can a v-on directive directly modify a data() property of the parent?

I have an application which has Vue components as children.
The components pass back data to the parent via a this.$emit (numberchnaged below), which is caught at the parent level by a v-on (or #) directive, which in turns triggers a method.
This method then updates a data() property of the parent:
<template>
(...)
<Users #numberchanged="doNumCh"></Users>
(...)
</template>
<script>
(...)
export default {
components: {
Users
},
data() {
return {
u: "hello"
}
},
methods: {
doNumCh(value) {
this.u = value
}
}
}
</script>
This solution works but is quite verbose for just updating this.u with what <Users> sent back.
Is there a way to make the update right in the <Users> tag, something like
<Users #numberchanged="u=theValueReturedByUsers"></Users>
My problem is that I do not know how to extract theValueReturedByUsers, I only get hold of it in the method as value (in my example above).
Functionally, you're looking to have v-model behavior on your component. Vue provides for that. So you can say
<template>
(...)
<Users v-model="u"></Users>
(...)
</template>
which is a tidy view, as long as your Users component (side note: you should always have a hyphen in custom component names) takes the value parameter and $emits the input event.
See also v-bind.sync to work with props other than value.
The payload is reachable via $event.
For the code above, the solution would therefore be
<Users #numberchanged="u=$event"></Users>
You can do it like this (without write a method in parent) with using the variable $event which contains the value returned (Object or literal variable) from child component:
<users #numberchanged="{u=$event}"></users>

can you pass component props to mapGetters vuex

I'm starting to make a universal input vue component. Before I worry about changing the data in the input, I just wanted to get the initial value from the store.
I have something like this:
<template lang="html">
<input class="input-text" type="text" name="" :value="value">
</template>
<script>
import store from '#/store/index';
export default {
name: 'InputText',
props: [
'dataModel',
'propertyName',
],
computed: {
value() {
return store.state[this.dataModel][this.propertyName];
},
},
};
</script>
This works. Instead of the value function in computed. I wanted to leverage mapGetters. So I tried something like this instead:
...mapGetters({
value: `${this.dataModel}/${this.propertyName}`,
}),
As soon as I tried the latter I got undefined for both values this.dataModel and this.propertyName. The context of this changes when invoking mapGetters because were in a new object. Is there a way to pass component props into mapGetters? Is there a way to set the context of this to be the component rather than the object argument? Or was my original approach/another approach the correct way to tackle this problem?
First, don t import the store in components, its available as this.$store, so remove:
import store from '#/store/index';
You can't use this like you are in mapGetters because it doesn't exist in the context youre trying to use it yet. The component is still early in it's lifecycle when when setting up properties. As far as I know, the name must be defined beforehand.

Categories