336

Let's say I have a main Vue instance that has child components. Is there a way of calling a method belonging to one of these components from outside the Vue instance entirely?

Here is an example:

var vm = new Vue({
  el: '#app',
  components: {
    'my-component': { 
      template: '#my-template',
      data: function() {
        return {
          count: 1,
        };
      },
      methods: {
        increaseCount: function() {
          this.count++;
        }
      }
    },
  }
});

$('#external-button').click(function()
{
  vm['my-component'].increaseCount(); // This doesn't work
});
<script src="http://vuejs.org/js/vue.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app">
  
  <my-component></my-component>
  <br>
  <button id="external-button">External Button</button>
</div>
  
<template id="my-template">
  <div style="border: 1px solid; padding: 5px;">
  <p>A counter: {{ count }}</p>
  <button @click="increaseCount">Internal Button</button>
    </div>
</template>

So when I click the internal button, the increaseCount() method is bound to its click event so it gets called. There is no way to bind the event to the external button, whose click event I am listening for with jQuery, so I'll need some other way to call increaseCount.

EDIT

It seems this works:

vm.$children[0].increaseCount();

However, this is not a good solution because I am referencing the component by its index in the children array, and with many components this is unlikely to stay constant and the code is less readable.

Pierce O'Neill
  • 385
  • 9
  • 22
harryg
  • 23,311
  • 45
  • 125
  • 198
  • 1
    I added an answer using mxins if you want to give it a try. In my opinion I prefer to setup the app this way. – Omar Tanti Mar 28 '18 at 01:31
  • Why do you need to do that? I mean why do you need to call that method? Maybe better to use something like v-model? – Igor Popov Feb 19 '22 at 01:48
  • In my case it would be something like this: I build some vue app that I include in some existing static hugo webpage. The vue app can be seen as some plugin/addon and is otherwise completely separate. But I would like to connect the existing search button and language switcher to the vue app that also supports this. – E. Körner May 22 '23 at 07:14

15 Answers15

352

In the end I opted for using Vue's ref directive. This allows a component to be referenced from the parent for direct access.

E.g.

Have a component registered on my parent instance:

var vm = new Vue({
    el: '#app',
    components: { 'my-component': myComponent }
});

Render the component in template/html with a reference:

<my-component ref="foo"></my-component>

Now, elsewhere I can access the component externally

<script>
vm.$refs.foo.doSomething(); //assuming my component has a doSomething() method
</script>

See this fiddle for an example: https://jsfiddle.net/0zefx8o6/

(old example using Vue 1: https://jsfiddle.net/6v7y6msr/)

Edit for Vue3 - Composition API

The child-component has to return the function in setup you want to use in the parent-component otherwise the function is not available to the parent.

Note: <script setup> doc is not affacted, because it provides all the functions and variables to the template by default.

h3xStream
  • 6,293
  • 2
  • 47
  • 57
harryg
  • 23,311
  • 45
  • 125
  • 198
  • 6
    That's because you probably haven't defined it. Look at the linked fiddle. – harryg Nov 09 '16 at 14:42
  • 38
    If you are using webpack then you won't be able to access `vm` due to the way it scopes modules. You can do something like `window.app = vm` in your `main.js`. Source: https://forum.vuejs.org/t/how-to-access-vue-from-chrome-console/3606 – tw airball Dec 01 '16 at 06:36
  • 1
    Can this approach be consider as normal? Or is this a hack? – jofftiquez Jun 30 '17 at 07:27
  • 3
    There's so official definition of what's a hack vs what's "normal" coding, but rather than call this approach a hack (or look for a "less-hacky" way of achieving the same thing) it's probably better to question why you need to do this. In many cases it might be more elegant to use Vue's event system to trigger external component behaviour, or even ask why you're triggering components externally at all. – harryg Jun 30 '17 at 08:30
  • 9
    This can come up if you are trying to integrate a view component into an already existing page. Rather than redesigning the page entirely, it is nice sometimes to incrementally add additional functionality. – Allen Wang Mar 23 '18 at 14:05
  • It is kind of normal for Vue as it lends itself to building out bits in simple ways without having to refactor all of your html. If you need to do this, its because your structure is already pretty questionable; but Vue works very well under questionable project layouts. – Shammoo Mar 23 '18 at 15:33
  • I used it, and it works wonders. It's great for creating submodules, that way your vue file does not become a mess. – Marco May 22 '18 at 18:57
  • @harryg - Thanks for your tip! There doesn't seem to be any need to register the component (using `components: { 'my-component': myComponent }`) in order for this to work. Is this a difference in versions of Vue? Or am I doing something unsafe if I leave that out? – MikeBeaton Dec 05 '18 at 09:48
  • 5
    I had to use this. : this.$refs.foo.doSomething(); – Eyal Jan 04 '19 at 15:08
  • 1
    for me this doesn't work, foo is just a reference to the DOM element itself – gyozo kudor Dec 06 '19 at 11:58
  • Be aware that if your ref element is inside a v-if block it will be `undefined` when the if block is toggled off. – Abraham Brookes Aug 17 '21 at 01:04
  • It may have changed lately (not sure) but you need to provide a `defineExpose` to make that one work tho. Check this other answer: https://stackoverflow.com/a/72348802/8816585 – kissu Oct 24 '22 at 10:32
55

You can set ref for child components then in parent can call via $refs:

Add ref to child component:

<my-component ref="childref"></my-component>

Add click event to parent:

<button id="external-button" @click="$refs.childref.increaseCount()">External Button</button>

var vm = new Vue({
  el: '#app',
  components: {
    'my-component': { 
      template: '#my-template',
      data: function() {
        return {
          count: 1,
        };
      },
      methods: {
        increaseCount: function() {
          this.count++;
        }
      }
    },
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  
  <my-component ref="childref"></my-component>
  <button id="external-button" @click="$refs.childref.increaseCount()">External Button</button>
</div>
  
<template id="my-template">
  <div style="border: 1px solid; padding: 2px;" ref="childref">
    <p>A counter: {{ count }}</p>
    <button @click="increaseCount">Internal Button</button>
  </div>
</template>
Jesse Reza Khorasanee
  • 3,140
  • 4
  • 36
  • 53
Cong Nguyen
  • 3,199
  • 1
  • 23
  • 22
  • 1
    Great answer. I'm just going to edit the html to be a bit smaller vertically. Currently I can only see 'Internal Button' when I run it which was can be confusing. – Jesse Reza Khorasanee Nov 28 '19 at 00:57
  • You can access from the parent component a ref using this : `this.$refs.childref`, used this to make a generic alert component for example – Kwaadpepper Oct 01 '20 at 06:39
50

For Vue2 this applies:

var bus = new Vue()

// in component A's method

bus.$emit('id-selected', 1)

// in component B's created hook

bus.$on('id-selected', function (id) {

  // ...
})

See here for the Vue docs. And here is more detail on how to set up this event bus exactly.

If you'd like more info on when to use properties, events and/ or centralized state management see this article.

See below comment of Thomas regarding Vue 3.

tony19
  • 125,647
  • 18
  • 229
  • 307
musicformellons
  • 12,283
  • 4
  • 51
  • 86
  • 1
    Short and sweet! If you dislike the global `bus` variable, you can go a step further and inject the bus into your component using [props](https://vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props). I'm relatively new to vue so I can't assure you that this is idiomatic. – byxor Aug 31 '18 at 14:13
  • 1
    new Vue() is [deprecated in vue 3](https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed) for alternative follow [this question](https://stackoverflow.com/questions/63471824/vue-3-event-bus) – Thomas Sep 07 '20 at 08:33
  • Event bus is not recommended patter (at least in vue 2/3), that is why it has been removed from docs. for more information you can [read this](https://discord.com/channels/325477692906536972/546534697166045204/863434241509294090) or the same on from image [here](https://snipboard.io/fiSJWX.jpg) - the answer was provided by skirtle (moderator, MVP on Vue's the discord chennel). "References to event buses were removed from the Vue 2 docs a long time ago and we recently added something to the Vue 3 docs to actively [discourage it](https://v3.vuejs.org/guide/migration/events-api.html#event-bus)" – Utmost Creator Sep 18 '21 at 22:53
33

You can use Vue event system

vm.$broadcast('event-name', args)

and

 vm.$on('event-name', function())

Here is the fiddle: http://jsfiddle.net/hfalucas/wc1gg5v4/59/

Helder Lucas
  • 3,273
  • 2
  • 21
  • 26
  • 6
    @GusDeCooL the example has been edited. Not that after Vuejs 2.0 some methods used there have been deprecated – Helder Lucas Jan 08 '17 at 18:59
  • 1
    Works well if there is only 1 instance of component, but if there are many instances, works better use $refs.component.method() – Koronos Dec 22 '17 at 03:36
31

A slightly different (simpler) version of the accepted answer:

Have a component registered on the parent instance:

export default {
    components: { 'my-component': myComponent }
}

Render the component in template/html with a reference:

<my-component ref="foo"></my-component>

Access the component method:

<script>
    this.$refs.foo.doSomething();
</script>
urig
  • 16,016
  • 26
  • 115
  • 184
Victor
  • 893
  • 1
  • 10
  • 25
12

Say you have a child_method() in the child component:

export default {
    methods: {
        child_method () {
            console.log('I got clicked')
        }
    }
}

Now you want to execute the child_method from parent component:

<template>
    <div>
        <button @click="exec">Execute child component</button>
        <child-cmp ref="child"></child_cmp> <!-- note the ref="child" here -->
    </div>
</template>

export default {
    methods: {
        exec () { //accessing the child component instance through $refs
            this.$refs.child.child_method() //execute the method belongs to the child component
        }
    }
}

If you want to execute a parent component method from child component:

this.$parent.name_of_method()

NOTE: It is not recommended to access the child and parent component like this.

Instead as best practice use Props & Events for parent-child communication.

If you want communication between components surely use vuex or event bus

Please read this very helpful article


Roland
  • 24,554
  • 4
  • 99
  • 97
  • Yes, you can, but not considered 'best practice': downward to Child use properties, upward to Parent use events. To also cover 'sidewards', use custom events, or e.g. vuex. See this [nice article](https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87) for more info. – musicformellons Aug 31 '18 at 14:28
  • Yes it is not recommended to do so. – Roland Sep 01 '18 at 06:05
7

This is a simple way to access a component's methods from other component

// This is external shared (reusable) component, so you can call its methods from other components

export default {
   name: 'SharedBase',
   methods: {
      fetchLocalData: function(module, page){
          // .....fetches some data
          return { jsonData }
      }
   }
}

// This is your component where you can call SharedBased component's method(s)
import SharedBase from '[your path to component]';
var sections = [];

export default {
   name: 'History',
   created: function(){
       this.sections = SharedBase.methods['fetchLocalData']('intro', 'history');
   }
}
Dotnetgang
  • 81
  • 1
  • 1
5

If you're using Vue 3 with <script setup> sugar, note that internal bindings of a component are closed (not visible from outside the component) and you must use defineExpose(see docs) to make them visible from outside. Something like this:

<script setup lang="ts">
const method1 = () => { ... };
const method2 = () => { ... };

defineExpose({
  method1,
  method2,
});
</script>

Since

Components using are closed by default

kissu
  • 40,416
  • 14
  • 65
  • 133
dotNET
  • 33,414
  • 24
  • 162
  • 251
4

Using Vue 3:

const app = createApp({})

// register an options object
app.component('my-component', {
  /* ... */
})

....

// retrieve a registered component
const MyComponent = app.component('my-component')

MyComponent.methods.greet();

https://v3.vuejs.org/api/application-api.html#component

Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
3

Here is a simple one

this.$children[indexOfComponent].childsMethodName();
Nick Web
  • 3
  • 1
Pratik Khadtale
  • 265
  • 4
  • 11
2

I am not sure is it the right way but this one works for me.
First import the component which contains the method you want to call in your component

import myComponent from './MyComponent'

and then call any method of MyCompenent

myComponent.methods.doSomething()
greybeard
  • 2,249
  • 8
  • 30
  • 66
  • 1
    this doesn't give you access to any data in your component. if your `doSomething` is using any thing from data, this method is useless. – cyboashu Jun 25 '20 at 23:30
  • @cyboashu you are right but it is a perfect idea for me since I wanted to use generic methods from a mixin. – Bogdan M. Jun 23 '21 at 11:06
1

Sometimes you want to keep these things contained within your component. Depending on DOM state (the elements you're listening on must exist in DOM when your Vue component is instantiated), you can listen to events on elements outside of your component from within your Vue component. Let's say there is an element outside of your component, and when the user clicks it, you want your component to respond.

In html you have:

<a href="#" id="outsideLink">Launch the component</a>
...
<my-component></my-component>

In your Vue component:

    methods() {
      doSomething() {
        // do something
      }
    },
    created() {
       document.getElementById('outsideLink').addEventListener('click', evt => 
       {
          this.doSomething();
       });
    }
    
ODaniel
  • 566
  • 4
  • 13
1

Declare your function in a component like this:

export default {
  mounted () {
    this.$root.$on('component1', () => {
      // do your logic here :D
    });
  }
};

and call it from any page like this:

this.$root.$emit("component1");
norbekoff
  • 1,719
  • 1
  • 10
  • 21
1

To access the data of a child component in Vue 3 or execute (call) a method, using <script setup> syntax, as mentioned above, it is necessary to use the defineExpose(see docs) method to make variables or methods visible from outside. Look at the following code.

Child component:

<script setup>
impor { ref } from "vue";

const varA = ref("Hola");
const myMethod = () => { ... };


defineExpose({
  varA,
  myMethod,
});
</script>

Parent component:

<child-component ref="myChildComponent" />

<script setup>
import { ref } from "vue";
const myChildComponent = ref(null)

const changeData = () => {
   myChildComponent.value.myMethod();

   // or
   
   myChildComponent.value.varA = "Hi";
}
</script>

As mentioned above:

Components using script setup are closed by default

Jonathan Arias
  • 471
  • 7
  • 18
-9

I have used a very simple solution. I have included a HTML element, that calls the method, in my Vue Component that I select, using Vanilla JS, and I trigger click!

In the Vue Component, I have included something like the following:

<span data-id="btnReload" @click="fetchTaskList()"><i class="fa fa-refresh"></i></span>

That I use using Vanilla JS:

const btnReload = document.querySelector('[data-id="btnReload"]');
btnReload.click();                
Gorbas
  • 82
  • 1
  • 3