Access instance via methods inside a custom Vue component - vue.js

I'm trying to access the instance methods/data through a triggered method inside a custom registered Vue component.
Below a basic example:
Vue.component('example-component', {
template: `<div>
<h2>Count: {{count}}</h2>
<button class="btn btn-primary" v-on:click="increment()">Increment</button>
</div>`,
data: () => {
return {
count: 0
}
},
methods: {
increment: () => {
console.log("Click!");
console.log("Current count: ", this.count);
this.count++;
console.log("New count: ", this.count);
},
decrement: () => {
// other function
}
},
mounted: () => {
console.log("Example component mounted!");
}
});
Results:
Example component mounted!
Click!
Current count: undefined
New count: NaN
As you might notice the property 'count' has been loaded during the component mount and is available/rendered inside the HTML. The method 'increment()' has also been triggered. However, 'this.count' seems to be unreachable like possible other methods (e.g. 'this.decrement()') which will throw a TypeError this.decrement is not a function.
Any suggestions if this approach is even possible?
PS. I'm aware of the default approach via a .vue file registery like:
Vue.component('example-component', require('./components/ExampleComponent.vue').default);

Explanation from the official docs:
Vue automatically binds the this value for methods so that it always refers to the component instance. This ensures that a method retains the correct this value if it's used as an event listener or callback. You should avoid using arrow functions when defining methods, as that prevents Vue from binding the appropriate this value.
The answer above by Phoenix seems to be valid, and I can only add that you can write the functions in a short form too like:
increment() { ... },
decrement() { ... }
which looks nicer in my opinion, although there is a slight difference.

Arrow functions don't bind with this. Use normal functions instead for your methods.
increment: function() { ... },
decrement: function() { ... }

Related

How to access "this" from Uppy's callback event

I'm using the Uppy Vue component library, and following the docs, I've initialized Uppy by adding it as a computed property.
computed: {
uppy: () => new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: '/',
}).on('complete', (result) => {
this.testing = 'success';
console.log('successful files:', result.successful);
console.log('failed files:', result.failed);
}),
}
I'm trying to update my Vue component's data now by using Uppy's complete event, but "this" is not defined. I'm not quite sure how to access "this" from here.
Any idea how to go about doing this?
Update
After posting this, I found a solution that works. I'm hesitant with this solution though as it seemed too easy.
If no one provides a better solution, I'll add this as the answer.
// Uppy Instance
uppy: function() {
return new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: '/',
}).on('complete', (result) => {
this.testing = 'success';
console.log('successful files:', result.successful);
console.log('failed files:', result.failed);
})
},
By following the Uppy docs and instantiating the Uppy instance with an arrow function, this no longer seems to refer to the Vue. This makes it so that accessing this.method(), or this.variable, etc. no longer works.
My solution was to change the Uppy instantiation from an arrow function to a regular function. I believe this causes this to refer to the global instance, but I don't have a solid understanding of this, so take what I say with a grain of salt.
I changed this:
computed: {
uppy: () => new Uppy()
}
To this:
computed: {
uppy: function() { return new Uppy() }
}

Open modal dialog on event bus event

I've created a backend and am now trying to build a frontend, using it. I'm very new to Vue.js and am having a hard time telling it to do what I want; probably because of missing some basic concepts. Hopefully someone can point me in the right direction.
The App.vue groups following components: Header, main section (routed), footer and a modal login dialog.
The issue I'm trying to solve is to display the modal login dialog when clicking the Login button (which lives in the header component); currently, nothing besides the messages being logged happens.
For this I've created an event bus and am firing an event:
export default {
name: 'SppdTeamTunerHeader',
methods: {
emitShowLoginDialogEvent () {
EventBus.$emit('ShowLoginDialog', true)
}
}
}
Emitting the event works as I can see in the Vue DevTools for Chrome.
Here's the complete code of App.vue:
<template>
<div id="app">
<SppdTeamTunerHeader/>
<router-view></router-view>
<SppdTeamTunerFooter/>
<LoginDialogModal
v-show="isLoginDialogVisible"
/>
</div>
</template>
<script>
import SppdTeamTunerHeader from '#/components/TheHeader'
import SppdTeamTunerFooter from '#/components/TheFooter'
import LoginDialogModal from '#/components/LoginDialogModal'
import { EventBus } from '#/common/EventBus'
export default {
name: 'App',
components: {
SppdTeamTunerHeader,
SppdTeamTunerFooter,
LoginDialogModal
},
data: function () {
return {
isLoginDialogVisible: false
}
},
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + this.isLoginDialogVisible)
if (isVisible) {
this.isLoginDialogVisible = true
} else {
this.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + this.isLoginDialogVisible)
})
},
destroyed () {
EventBus.$off('ShowLoginDialog')
}
}
</script>
When checking the console, following is being printed when clicking the login button:
Setting ShowLoginDialog isVisible=true. isLoginDialogVisible=undefined
Finished setting isLoginDialogVisible=true
The value logged for isLoginDialogVisible can't come from the variable defined in the data function as it prints undefined, whereas it has been defined as false (I guess that's my main problem).
I've read quite a few articles about the subject, e.g:
https://codingexplained.com/coding/front-end/vue-js/why-components-data-properties-must-be-functions
https://v2.vuejs.org/v2/guide/instance.html#Data-and-Methods
The modal dialog example I've based the implementation comes from here: https://alligator.io/vuejs/vue-modal-component/
This is happening because you are not using an Arrow function. Instead of a plain function, use arrow function like this:
mounted () {
// Note the use of arrow function.
EventBus.$on('ShowLoginDialog', (isVisible) => {
// .. All your code
})
}
If you use plain function function () {}, then this pointer is not accessible within inner function. Arrow function will lexically bind this pointer to mounted() function's this context. So use an arrow function i.e. () => {};
Note: If you insist on using plain old function syntax then use closure variable to keep track of this pointer:
mounted () {
// Assign this pointer to some closure variable
const vm = this;
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + vm.isLoginDialogVisible)
if (isVisible) {
vm.isLoginDialogVisible = true
} else {
vm.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + vm.isLoginDialogVisible)
})
}
This has nothing to do with Vue.js. It is a typical JavaScript behavior.
I believe your listener for the EventBus events needs to be accessible to App. Right now EventBus and App are two separate instances. You could mount the event handler inside App like this:
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
...
});

How to pass variable from Vuex store to a function as value?

So I am working with what would appear to be a simple issue, but it is eluding me this evening. I have a value that is set in a Vuex store. In my component file, I declare a constant where the value is retrieved from the store. Everything up to this point works perfectly.
Then, upon submitting a form in the component a script function is run. Within that function, I need to pass the value from the Vuex store along with a couple of other arguments to another function. The function gets call, the arguments are passed, and it all works as expected.
However ... I am getting console errors stating ...
Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] do not mutate vuex store state outside mutation handlers.
What is the correct what to retrieve a value from the Vuex store and then pass that value to a function?
Some more detail here ... Page 1 stores an object representing a CognitoUser in the store using a mutation function which works as expected, then transitions to Page 2. Page 2 retrieves the object from the store (tried both the data and computed methods mentioned below as well as using the getter directly in the code - all fail the same). Within a method on Page 2, the object from the store is accessible. However, that method attempts to call the Amplify completeNewPassword method, passing the CongnitoUser object as an argument. This is the point that the error appears stating that the mutation handler should be used even though there is no change to the object on my end.
....
computed: {
user: {
get(){
return this.$store.getters[ 'security/localUser' ]
},
set( value ){
this.$store.commit( 'security/setLocalUser', value )
}
}
},
....
methods: {
async submitForm(){
this.$Amplify.Auth.completeNewPassword( this.user, this.model.password, this.requiredAttributes )
.then( data => {
....
This is almost certainly a duplicate question. You can refer to my answer here.
Basically you should pass the Vuex value to a local data item and use that in your component function. Something like this.
<script>
export default {
data: () => ({
localDataItem: this.$store.getters.vuexItem,
})
methods: {
doSomething() {
use this.localDataItem.here
}
}
}
</script>
The canonical way of handling this by using computed properties. You define a computed property with getter and setter and proxy access to vuex thru it.
computed: {
localProperty: {
get: function () {
return this.$store.getters.data
},
set: function (val) {
this.$store.commit(“mutationName”, val )
}
}
}
Now you can use localProperty just as we use any other property defined on data. And all the changes get propagated thru the store.
Try if this work
<template>
<div>
<input :value="user" #change="onChangeUser($event.target.value)"></input>
</div>
</template>
<script>
computed: {
user() {
return this.$store.getters[ 'security/localUser' ]
}
},
methods: {
onChangeUser(user) {
this.$store.commit( 'security/setLocalUser', user );
},
async submitForm(){
this.$Amplify.Auth.completeNewPassword( this.user, this.model.password, this.requiredAttributes )
.then( data => {
...
}
</script>

create method dynamically in vue.js

nomally we predefine methods in vue.js like below.
methods : {
func1: function(){
}
}
and call a function in template
<button #click="func1">click</button>
is it possible to add method dynamically in vue.js?
[for example]
//actually $methods is not exist. i checked $data is exist. so it is my guess.
this.$methods["func2"] = function(){
}
in angular.js it is possible like this.
$scope["function name"] = function(){
}
Functions in javascript are like any other variable, so there are various ways you can dynamically add functions. A very simple solution would look like this:
<template>
<div id="app">
<div #click="userFuncs.myCustomFunction">Click me!</div>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
// These contain all dynamic user functions
userFuncs: {}
}
},
created () {
window.setTimeout(() => {
this.$set(this.userFuncs, 'myCustomFunction', () => {
console.log('whoohoo, it was added dynamically')
})
}, 2000)
}
};
</script>
It will however give off warnings and potentially errors when the function is invoked while there is no function attached. We can get around this by having a boilerplate function that executes a default function unless a new function is defined.
We would then change the template to:
<div #click="executeDynamic('myCustomFunction')">Click me!</div>
and add the following to the component:
methods: {
executeDynamic (name) {
if (this.userFuncs[name]) {
this.userFuncs[name]()
} else {
console.warn(`${name} was not yet defined!`)
}
}
}
You should always try to use Vue's event handlers via #someEvent or v-on:someEvent handlers, because Vue will automatically attach and detach event handlers when appropriate. In very very very rare cases something you may want to do may not be possible with Vue, you can attach event handlers yourself. Just make sure you use the beforeDestroy hook to remove them again.

Vue: $emit to parents of parents [duplicate]

It seems that Vue.js 2.0 doesn't emit events from a grand child to his grand parent component.
Vue.component('parent', {
template: '<div>I am the parent - {{ action }} <child #eventtriggered="performAction"></child></div>',
data(){
return {
action: 'No action'
}
},
methods: {
performAction() { this.action = 'actionDone' }
}
})
Vue.component('child', {
template: '<div>I am the child <grand-child></grand-child></div>'
})
Vue.component('grand-child', {
template: '<div>I am the grand-child <button #click="doEvent">Do Event</button></div>',
methods: {
doEvent() { this.$emit('eventtriggered') }
}
})
new Vue({
el: '#app'
})
This JsFiddle solves the issue https://jsfiddle.net/y5dvkqbd/4/ , but by emtting two events:
One from grand child to middle component
Then emitting again from middle component to grand parent
Adding this middle event seems repetitive and unneccessary. Is there a way to emit directly to grand parent that I am not aware of?
Vue 2.4 introduced a way to easily pass events up the hierarchy using vm.$listeners
From https://v2.vuejs.org/v2/api/#vm-listeners :
Contains parent-scope v-on event listeners (without .native modifiers). This can be passed down to an inner component via v-on="$listeners" - useful when creating transparent wrapper components.
See the snippet below using v-on="$listeners" in the grand-child component in the child template:
Vue.component('parent', {
template:
'<div>' +
'<p>I am the parent. The value is {{displayValue}}.</p>' +
'<child #toggle-value="toggleValue"></child>' +
'</div>',
data() {
return {
value: false
}
},
methods: {
toggleValue() { this.value = !this.value }
},
computed: {
displayValue() {
return (this.value ? "ON" : "OFF")
}
}
})
Vue.component('child', {
template:
'<div class="child">' +
'<p>I am the child. I\'m just a wrapper providing some UI.</p>' +
'<grand-child v-on="$listeners"></grand-child>' +
'</div>'
})
Vue.component('grand-child', {
template:
'<div class="child">' +
'<p>I am the grand-child: ' +
'<button #click="emitToggleEvent">Toggle the value</button>' +
'</p>' +
'</div>',
methods: {
emitToggleEvent() { this.$emit('toggle-value') }
}
})
new Vue({
el: '#app'
})
.child {
padding: 10px;
border: 1px solid #ddd;
background: #f0f0f0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<parent></parent>
</div>
NEW ANSWER (Nov-2018 update)
I discovered that we could actually do this by leveraging the $parent property in the grand child component:
this.$parent.$emit("submit", {somekey: somevalue})
Much cleaner and simpler.
The Vue community generally favors using Vuex to solve this kind of issue. Changes are made to Vuex state and the DOM representation just flows from that, eliminating the need for events in many cases.
Barring that, re-emitting would probably be the next best choice, and lastly you might choose to use an event bus as detailed in the other highly voted answer to this question.
The answer below is my original answer to this question and is not an approach I would take now, having more experience with Vue.
This is a case where I might disagree with Vue's design choice and resort to DOM.
In grand-child,
methods: {
doEvent() {
try {
this.$el.dispatchEvent(new Event("eventtriggered"));
} catch (e) {
// handle IE not supporting Event constructor
var evt = document.createEvent("Event");
evt.initEvent("eventtriggered", true, false);
this.$el.dispatchEvent(evt);
}
}
}
and in parent,
mounted(){
this.$el.addEventListener("eventtriggered", () => this.performAction())
}
Otherwise, yes, you have to re-emit, or use a bus.
Note: I added code in the doEvent method to handle IE; that code could be extracted in a reusable way.
Yes, you're correct events only go from child to parent. They don't go further, e.g. from child to grandparent.
The Vue documentation (briefly) addresses this situation in the Non Parent-Child Communication section.
The general idea is that in the grandparent component you create an empty Vue component that is passed from grandparent down to the children and grandchildren via props. The grandparent then listens for events and grandchildren emit events on that "event bus".
Some applications use a global event bus instead of a per-component event bus. Using a global event bus means you will need to have unique event names or namespacing so events don't clash between different components.
Here is an example of how to implement a simple global event bus.
If you want to be flexible and simply broadcast an event to all parents and their parents recursively up to the root, you could do something like:
let vm = this.$parent
while(vm) {
vm.$emit('submit')
vm = vm.$parent
}
Another solution will be on/emit at root node:
Uses vm.$root.$emit in grand-child, then uses vm.$root.$on at the ancestor (or anywhere you'd like).
Updated: sometimes you'd like to disable the listener at some specific situations, use vm.$off (for example: vm.$root.off('event-name') inside lifecycle hook=beforeDestroy).
Vue.component('parent', {
template: '<div><button #click="toggleEventListener()">Listener is {{eventEnable ? "On" : "Off"}}</button>I am the parent - {{ action }} <child #eventtriggered="performAction"></child></div>',
data(){
return {
action: 1,
eventEnable: false
}
},
created: function () {
this.addEventListener()
},
beforeDestroy: function () {
this.removeEventListener()
},
methods: {
performAction() { this.action += 1 },
toggleEventListener: function () {
if (this.eventEnable) {
this.removeEventListener()
} else {
this.addEventListener()
}
},
addEventListener: function () {
this.$root.$on('eventtriggered1', () => {
this.performAction()
})
this.eventEnable = true
},
removeEventListener: function () {
this.$root.$off('eventtriggered1')
this.eventEnable = false
}
}
})
Vue.component('child', {
template: '<div>I am the child <grand-child #eventtriggered="doEvent"></grand-child></div>',
methods: {
doEvent() {
//this.$emit('eventtriggered')
}
}
})
Vue.component('grand-child', {
template: '<div>I am the grand-child <button #click="doEvent">Emit Event</button></div>',
methods: {
doEvent() { this.$root.$emit('eventtriggered1') }
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<parent></parent>
</div>
VueJS 2 components have a $parent property that contains their parent component.
That parent component also includes its own $parent property.
Then, accessing the "grandparent" component it's a matter of accessing the "parent's parent" component:
this.$parent["$parent"].$emit("myevent", { data: 123 });
Anyway, this is kinda tricky, and I recommend using a global state manager like Vuex or similar tools, as other responders have said.
I've made a short mixin based on #digout answer. You want to put it, before your Vue instance initialization (new Vue...) to use it globally in project. You can use it similarly to normal event.
Vue.mixin({
methods: {
$propagatedEmit: function (event, payload) {
let vm = this.$parent;
while (vm) {
vm.$emit(event, payload);
vm = vm.$parent;
}
}
}
})
Riffing off #kubaklam and #digout's answers, this is what I use to avoid emitting on every parent component between the grand-child and the (possibly distant) grandparent:
{
methods: {
tunnelEmit (event, ...payload) {
let vm = this
while (vm && !vm.$listeners[event]) {
vm = vm.$parent
}
if (!vm) return console.error(`no target listener for event "${event}"`)
vm.$emit(event, ...payload)
}
}
}
When building out a component with distant grand children where you don't want many/any components to be tied to the store, yet want the root component to act as a store/source of truth, this works quite well. This is similar to the data down actions up philosophy of Ember. Downside is that if you want to listen for that event on every parent in between, then this won't work. But then you can use $propogateEmit as in above answer by #kubaklam.
Edit: initial vm should be set to the component, and not the component's parent. I.e. let vm = this and not let vm = this.$parent
This is the only case when I use event bus!! For passing data from deep nested child, to not directly parent, communication.
First: Create a js file (I name it eventbus.js) with this content:
import Vue from 'vue'
Vue.prototype.$event = new Vue()
Second: In your child component emit an event:
this.$event.$emit('event_name', 'data to pass')
Third: In the parent listen to that event:
this.$event.$on('event_name', (data) => {
console.log(data)
})
Note: If you don't want that event anymore please unregister it:
this.$event.$off('event_name')
INFO: No need to read the below personal opinion
I don't like to use vuex for grand-child to grand-parent communication (Or similar communication level).
In vue.js for passing data from grand-parent to grand-child you can use provide/inject. But there is not something similar for the opposite thing. (grand-child to grand-parent) So I use event bus whenever I have to do that kind of communication.
Riffing off #digout answer. I am thinking that if the purpose is to send data to a far-ancestor then we don't need $emit at all. I did this for my edge-case and it seems to work. Yes, it could be implemented via a mixin but it doesn't have to be.
/**
* Send some content as a "message" to a named ancestor of the component calling this method.
* This is an edge-case method where you need to send a message many levels above the calling component.
* Your target component must have a receiveFromDescendant(content) method and it decides what
* to do with the content it gets.
* #param {string} name - the name of the Vue component eg name: 'myComponentName'
* #param {object} content - the message content
*/
messageNamedAncestor: function (name, content) {
let vm = this.$parent
let found = false
while (vm && !found) {
if (vm.$vnode.tag.indexOf('-' + name) > -1) {
if (vm.receiveFromDescendant) {
found = true
vm.receiveFromDescendant(content)
} else {
throw new Error(`Found the target component named ${name} but you dont have a receiveFromDescendant method there.`)
}
} else {
vm = vm.$parent
}
}
}
Given an ancestor:
export default {
name: 'myGreatAncestor',
...
methods: {
receiveFromDescendant (content) {
console.log(content)
}
}
}
A great grand-child says
// Tell the ancestor component something important
this.messageNamedAncestor('myGreatAncestor', {
importantInformation: 'Hello from your great descendant'
})
As of Vue 3, a number of fundamental changes have happened to root events:
The $on, $off and $once root methods no longer exist. There is to a certain extent something to replace this, since you can listen to root events by doing this:
createApp(App, {
// Listen for the 'expand' event
onExpand() {
console.log('expand')
}
})
Another solution are event buses, but the Vue.js documents take a dim view - they can cause maintenance headaches in the long run. You might get an ever spreading set of emits and event sinks, with no clear or central idea of how it is managed or what components could be affected elsewhere. Nonetheless, examples given by the docs of event buses are mitt and tiny-emitter.
However the docs make it clear that they recommend handling these sorts of situations in this order:
Props A convenient solution for parent / child communications.
Provide/Inject A simple way for ancestors to communicate with their descendants (although critically, not the other way around).
Vuex A way of handling global state in a clear fashion. It's important to note that this is not solely for events, or communications - Vuex was built primarily to handle state.
Essentially the choice for the OP would come down to using an event bus, or Vuex. In order to centralise the event bus, you could place it inside Vuex, if state was also needed to be globally available. Otherwise using an event bus with strict centralised controls on it's behaviour and location might help.
I really dig the way this is handled by creating a class that is bound to the window and simplifying the broadcast/listen setup to work wherever you are in the Vue app.
window.Event = new class {
constructor() {
this.vue = new Vue();
}
fire(event, data = null) {
this.vue.$emit(event, data);
}
listen() {
this.vue.$on(event, callback);
}
}
Now you can just fire / broadcast / whatever from anywhere by calling:
Event.fire('do-the-thing');
...and you can listen in a parent, grandparent, whatever you want by calling:
Event.listen('do-the-thing', () => {
alert('Doing the thing!');
});