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.
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: <sript setup> doc is not affacted, because it provides all the functions and variables to the template by default.
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>
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.
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/
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>
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
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');
}
}
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
Here is a simple one
this.$children[indexOfComponent].childsMethodName();
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()
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");
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
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:
Launch the component
...
<my-component></my-component>
In your Vue component:
methods() {
doSomething() {
// do something
}
},
created() {
document.getElementById('outsideLink').addEventListener('click', evt =>
{
this.doSomething();
});
}
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();
Related
In vue is possible to bind button click directly from vue instance?
I have this button:
<el-button #click="alert_me" class="gf-button" type="primary" style="margin-left: 16px;">Button</el-button>
I wan't to remove #click="alert_me" and do like i would normally do with jquery but with vue.
Is it possible?
My Vue Instance:
new Vue({
el: "#app",
data: {
},
methods: {
alert_me() {
alert('Hello from vue!');
}
},
});
Thanks
If you need to attach a click event listener programmatically, it is possible with the classic javascript api:
<template>
<el-button class="gf-button" type="primary">Button</el-button>
</template>
<script>
export default {
mounted () {
// jquery would also work if it's installed.
document.getElementByClassName('gf-button').addEventListener('click', this.alert_me)
},
methods: {
alert_me() {
console.log('alert')
}
}
}
</script>
You could avoid the manual element query from the document with the Vue $refs object.
<template>
<el-button ref="myButton" class="gf-button" type="primary">Button</el-button>
</template>
<script>
export default {
mounted () {
this.$refs.myButton.addEventListener('click', this.alert_me)
},
methods: {
alert_me() {
console.log('alert')
}
}
}
</script>
But if you need that event as soon as the Vue component is created, I wouldn't recommend doing this. It kinda oversee the shadow dom optimisation of Vue.
The #click="" syntax provided is the best way to attach a click listener to an html element.
You can make use of addEventListener and call it in mounted life cycle.
mounted() {
document.querySelector('#element').addEventListener('click', event =>
{
//handle click
}
)
}
I am trying to catch situation, when component is not found, ie:
{
template: '<some-unknown-component></some-unknown-component>'
}
At that moment, Vue warns us with unknown custom element: <some-unknown-component>...
I would like to step in when some-unknown-component is not found and then use another component instead, like stub-component:
{
name: 'stub-component',
props: ['componentName'],
template: '<p>component ${componentName} does not exists, click here to create...</p>'
}
UPDATE: I am looking for solution without changing the template itself, so no v-if and component added.
Vue exposes a global error and warning handler. I managed to get a working solution by using the global warnHandler. I don't know if it is exactly what you are looking for, but it may be a good starting point. See the working snippet (I think it is quite self explanatory).
Vue.config.warnHandler = function (err, vm, info) {
if (err.includes("Unknown custom element:")) {
let componentName = err.match(/<.*>/g)[0].slice(1, -1)
vm.$options.components[componentName] = Vue.component('stub-component', {
props: ['componentName'],
template: `<p>component "${componentName}" does not exists, click here to create...</p>`,
});
vm.$forceUpdate()
} else {
console.warn(err)
}
};
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<unknown-component></unknown-component>
</div>
Vue stores the details of all the registered components in the $options.component property of the Vue instance.
So, you can check for the component availability using this.$options.component and if the component is present then load the component otherwise load the other component.
In the below example, suppose you have two different components and you want to load them on the availability, then you can create a computed property on the basis of it, load the component as needed.
var CustomComponent = Vue.extend({ template: '<h2>A custom Component</h2>' });
var AnotherComponent = Vue.extend({ template: '<h2>Custom component does not exist.</h2>' });
new Vue({
el: "#app",
components: {
CustomComponent,
AnotherComponent
},
computed: {
componentAvailable () {
return this.$options.components.CustomComponent
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="componentAvailable">
<custom-component />
</div>
<div v-else>
<another-component />
</div>
</div>
I know that in Vue parents should update the children through props and children should update their parents through events.
Assume this is my parent component .vue file:
<template>
<div>
<my-child-component :category="category"></my-child-component>
</div>
</template>
<script>
export default {
data: {
return {
category: 'Test'
}
}
}
</script>
When I update the category data in this component, it will also update the category props in my-child-component.
Now, when I want to use Vue in Laravel, I usually use an inline template and pass the value from the blade directly to my components (as for example also suggested at https://stackoverflow.com/a/49299066/2311074).
So the above example my my-parent-component.blade.php could look like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component inline-template>
<my-child-component :category="{{ $category }}"></my-child-component>
</my-parent-component>
But now my-parent-component is not aware about the data of category. Basically only the child knows the category and there is no communication between parent and child about it.
How can I pass the data from blade without breaking the parent and child communication?
I just had to pass the category to the inline-template component through props like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component :initcategory="{$category}}" inline-template>
<my-child-component v-model="category"></my-child-component>
</my-parent-component>
In my-parent-component I had to set the props and initialize is using the create method:
export default {
props: {
initcategory: '',
},
data() {
return {
category: '',
};
},
created(){
this.category = this.initcategory;
}
}
Now my my-parent-component is fully aware of the category and it can communicate to the child using props and $emit as usual.
Your reference to this answer is different altogether from what you are looking for!
He's binding the :userId prop of the example component but not the parent component or in simple words: Any template using the example vue can either pass a string prop or bind :userId prop to a string variable. Following is similar:
<example :userId="{{ Auth::user()->id }}"></example>
OR
<example :userId="'some test string'"></example>
So you should rather assign {{ $category }} to a data variable but rather binds to a child component prop which will have no effect on the parent.
In the following snippet you're only binding the string but rather a data key:
<my-child-component :category="{{ $category }}"></my-child-component>
Update
See the following example which will change the h1 title after 3 seconds
// HelloWorld.vue
<template>
<app-name :name="appName" #appNameChanged="appName = $event"></app-name>
</template>
<script>
export default {
props: ['name'],
data() {
return {
appName: null
}
},
mounted() {
// NOTE: since Strings are immutable and thus will assign the value while objects and arrays are copied by reference
// the following is just for the purpose of understanding how binding works
this.appName = this.name;
}
}
</script>
The template which renders the app title or you can say the child component
// AppName.vue
<template>
<h1>{{ name }}</h1>
</template>
<script>
export default {
props: ['name'],
mounted() {
setTimeout(() => {
this.$emit('appNameChanged', 'Change App')
}, 3000);
}
}
</script>
And here's how it is being used in the welcome.blade.php
<div id="app">
<hello-world :name="'Laravel App'"></hello-world>
</div>
I have been reading lots of articles about this, and it seems that there are multiple ways to do this with many authors advising against some implementations.
To make this simple I have created a really simple version of what I would like to achieve.
I have a parent Vue, parent.vue. It has a button:
<template>
<div>
<button v-on:click="XXXXX call method in child XXXX">Say Hello</button>
</div>
</template>
In the child Vue, child.vue I have a method with a function:
methods: {
sayHello() {
alert('hello');
}
}
I would like to call the sayHello() function when I click the button in the parent.
I am looking for the best practice way to do this. Suggestions I have seen include Event Bus, and Child Component Refs and props, etc.
What would be the simplest way to just execute the function in my method?
Apologies, this does seem extremely simple, but I have really tried to do some research.
Thanks!
One easy way is to do this:
<!-- parent.vue -->
<template>
<button #click="$refs.myChild.sayHello()">Click me</button>
<child-component ref="myChild" />
</template>
Simply create a ref for the child component, and you will be able to call the methods, and access all the data it has.
You can create a ref and access the methods, but this is not recommended. You shouldn't rely on the internal structure of a component. The reason for this is that you'll tightly couple your components and one of the main reasons to create components is to loosely couple them.
You should rely on the contract (interface in some frameworks/languages) to achieve this. The contract in Vue relies on the fact that parents communicate with children via props and children communicate with parents via events.
There are also at least 2 other methods to communicate when you want to communicate between components that aren't parent/child:
the event bus
vuex
I'll describe now how to use a prop:
Define it on your child component
props: ['testProp'],
methods: {
sayHello() {
alert('hello');
}
}
Define a trigger data on the parent component
data () {
return {
trigger: 0
}
}
Use the prop on the parent component
<template>
<div>
<childComponent :testProp="trigger"/>
</div>
</template>
Watch testProp in the child component and call sayHello
watch: {
testProp: function(newVal, oldVal) {
this.sayHello()
}
}
Update trigger from the parent component. Make sure that you always change the value of trigger, otherwise the watch won't fire. One way of doing this is to increment trigger, or toggle it from a truthy value to a falsy one (this.trigger = !this.trigger)
I don't like the look of using props as triggers, but using ref also seems as an anti-pattern and is generally not recommended.
Another approach might be: You can use events to expose an interface of methods to call on the child component this way you get the best of both worlds while keeping your code somehow clean. Just emit them at the mounting stage and use them when pleased. I stored it in the $options part in the below code, but you can do as pleased.
Child component
<template>
<div>
<p>I was called {{ count }} times.</p>
</div>
</template>
<script>
export default {
mounted() {
// Emits on mount
this.emitInterface();
},
data() {
return {
count: 0
}
},
methods: {
addCount() {
this.count++;
},
notCallable() {
this.count--;
},
/**
* Emitting an interface with callable methods from outside
*/
emitInterface() {
this.$emit("interface", {
addCount: () => this.addCount()
});
}
}
}
</script>
Parent component
<template>
<div>
<button v-on:click="addCount">Add count to child</button>
<child-component #interface="getChildInterface"></child-component>
</div>
</template>
<script>
export default {
// Add a default
childInterface: {
addCount: () => {}
},
methods: {
// Setting the interface when emitted from child
getChildInterface(childInterface) {
this.$options.childInterface = childInterface;
},
// Add count through the interface
addCount() {
this.$options.childInterface.addCount();
}
}
}
</script>
With vue 3 composition api you can do it like this:
Parent.vue
<script setup lang="ts">
const childRef = ref()
const callSayHello = () => {
childRef.value.sayHello()
}
</script>
<template>
<child ref="childRef"></child>
</template>
<style scoped></style>
Child.vue
<script setup lang="ts">
const sayHello = () => {
console.log('Hello')
}
defineExpose({ sayHello })
</script>
<template></template>
<style scoped></style>
I am not sure is this the best way. But I can explain what I can do...
Codesandbox Demo : https://codesandbox.io/s/q4xn40935w
From parent component, send a prop data lets say msg. Have a button at parent whenever click the button toggle msg true/false
<template>
<div class="parent">
Button from Parent :
<button #click="msg = !msg">Say Hello</button><br/>
<child :msg="msg"/>
</div>
</template>
<script>
import child from "#/components/child";
export default {
name: "parent",
components: { child },
data: () => ({
msg: false
})
};
</script>
In child component watch prop data msg. Whenever msg changes trigger a method.
<template>
<div class="child">I am Child Component</div>
</template>
<script>
export default {
name: "child",
props: ["msg"],
watch: {
msg() {
this.sayHello();
}
},
methods: {
sayHello() {
alert("hello");
}
}
};
</script>
This is an alternate take on Jonas M's excellent answer. Return the interface with a promise, no need for events. You will need a Deferred class.
IMO Vue is deficient in making calling child methods difficult. Refs aren't always a good option - in my case I need to call a method in one of a thousand grandchildren.
Parent
<child :getInterface="getInterface" />
...
export default {
setup(props) {
init();
}
async function init() {
...
state.getInterface = new Deferred();
state.childInterface = await state.getInterface.promise;
state.childInterface.doThing();
}
}
Child
export default {
props: {
getInterface: Deferred,
},
setup(props) {
watch(() => props.getInterface, () => {
if(!props.getInterface) return;
props.getInterface.resolve({
doThing: () => {},
doThing2: () => {},
});
});
}
}
I am building a form framework in vue. I have components for each field type. Each field type component uses this.$emit to communicate changes with the parent component.
I am able to trigger events in the parent component using v-on directives as follows:
<template>
<div v-if="fieldsLoaded">
<form-select :field="fields.title" v-on:updated="validate" ></form-select>
<form-input :field="fields.first_name" v-on:updated="validate" ></form-input>
</div>
</template>
However, I don't want to have to manually specify that every component should trigger the validate method individually.
How can I have the parent component listen for the updated emit across all its child components?
Edit: I'm looking for something like the below, though $on only catches emits that occur within the same component, rather than its children
created: function(){
this.$on('updated',validate)
}
The best way is to use event bus or even better in my opinion vuex.
For the first case take a look here
For the second here
With event bus you can emit an event, and listen to that event whenever you want(at parent,child even in the same component)
Vuex It serves as a centralized store for all the components in an application and you can have properties in that store,and you can use and manipulate them.
Example with event Bus:
main.js:
import Vue from 'vue'
import App from './App.vue'
export const eventBus = new Vue();
new Vue({
el: '#app',
render: h => h(App)
})
User Component
<template>
<button #click="clicked">Click me to create event</button>
</template>
<script>
import { eventBus } from './main'
export default {
name: 'User',
methods: {
clicked() {
eventBus.$emit('customEvent', 'a text to pass')
}
}
}
</script>
Admin component
<template>
<p>The message from event is: {{message}}</p>
</template>
<script>
import { eventBus } from './main'
export default {
name: 'Admin',
data: () => ({
message: ''
})
created() {
eventBus.$on('customEvent', dataPassed => {
this.message = dataPassed
}
}
}
</script>
Take a look to this tutorial to learn Vuex
For your case you can use v-model like following:
<template>
<div v-if="fieldsLoaded">
<form-select v-model="fields.title" :validate="validate" ></form-select>
<form-input v-model="fields.first_name" :validate="validate" ></form-input>
</div>
</template>
v-model is essentially syntax sugar for updating data on user input events.
<input v-model="something">
is just syntactic sugar for:
<input v-bind:value="something" v-on:input="something = $event.target.value">
You can pass a prop : value in the child components, and before changing input field call a function to validate which is also passed as a prop.
Vue.component('form-select', {
props: ['options', 'value', 'onChange', 'validate'], //Added one more prop
template: '#your-template',
mounted: function () {
},
methods: {
change (opt) {
if (this.validate !== undefined) {
var isValid = this.validate(this.value)
if(!isValid) return;
}
this.$emit('input', opt)
},
},
})