What i want to achieve is to build a loading vue directives,
function with v-loading directive will render spinner and block event till function promise resolve or reject.
What i had tried so far:
use addEventListener, but it can only listen dom's native event, not vue event
hijack vue $emit function, but get a warning said that don't override vue native function named $, even if this solution work, i think this is a bad solution.
in directives argument, binding.instance[binding.value.name] refer to onEvent function in component, i tried override it but it doesn't work. When onEvent trigger again, it run old onEvent before override.
third party event emitter(eg, mitt). this method works well, but custom-component have to write extra code to emit event.
As example code below,
user of v-loading have to remember to write 2 emit (mitt and vue's emit).
It is not that straight forward, and it has extra dependency.
// mitt solution
// custom-component template
<custom-component v-loading:event="onEvent">
// custom-component script
setup(props, {emit}) {
function emitEvent() {
emit("event");
// bad: have to remember to write this extra line, and it is third party dependency
mittEmitter.emit("event");
}
}
So, any other solution to listen vue's event(not dom's native event) from vue's $emit?
LoadingDirective.ts
import { Directive } from "vue";
const Loading: Directive = {
mounted(el, binding) {
const eventName = binding.arg;
const onEvent = binding.value;
// I want to listen vue's event(eventName) here
// do something extra
onEvent(); // original onEvent() function to run in App.vue
// do something extra
}
};
export default Loading;
App.vue
<template>
<!-- when onEvent triggered, a spinner will be render in custom-component -->
<custom-component v-loading:event="onEvent" />
</template>
CustomComponent.vue
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup(props, {emit}) {
function emitEvent() {
// use only vue's emit
emit("event")
}
return {
onEvent
};
}
});
</script>
The Vue 3 documentation recommends using an external library such as mitt or tiny-emitter.
JSFiddle Example
<template>
<div id="app">
<custom-component v-loading="eventHandler" />
</div>
</template>
const emitter = mitt();
const customComponent = { template: '<h1>Example</h1>' };
const app = Vue.createApp({
components: { customComponent },
setup() {
setTimeout(() => {
emitter.emit('loadingEvent', { colour: 'Green' });
}, 1000);
const eventHandler = e => console.log('Handled!', e);
return { eventHandler };
},
});
app.directive('loading', {
mounted(el, binding) {
const func = binding.value;
emitter.on('loadingEvent', data => {
// your logic here...
func(data);
});
},
});
app.mount('#app');
Related
<template>
<RecyclablesPopup ref="LVP" class="inline-block m-5px"></RecyclablesPopup>
</template>
<script setup>
import RecyclablesPopup from "../components/popups/RecyclablesPopup";
import { ref } from 'vue';
const LVP = ref(null);
// ... after mounted I have an event with a legacy component and onclick handler:
eventClick: function(calEvent)
{
console.log(LVP.value);
LVP.value.click();
}
</script>
At the end I get Uncaught TypeError: LVP.value.click is not a function after I clicked.
console.log returns me the proxy object as expected Proxy { <target>: Proxy, <handler>: {…} }
Why can't I call click()?
the click function should be exposed by the child component in order be accessed by the parent component :
RecyclablesPopup component
<script setup>
function click(){
//.......
}
defineExpose({
click
})
</script>
for more details please check https://vuejs.org/guide/essentials/template-refs.html#ref-on-component
If you are using script setup you can't access functions, variables, etc., defined inside the referenced component. To change that you have to use the defineExpose compiler macro inside RecyclablesPopup component - check more in documentation
//inside RecyclablesPopup
<script setup>
import { ref } from 'vue'
const click = () => {
//do something
}
defineExpose({
click,
})
</script>
You need to execute the function on the onMounted lifecycle to guarantee that the component is mounted to the DOM, as the value before the component is mounted will be undefined.
onMounted(() => {
eventClick()
})
For more resources
https://vuejs.org/api/composition-api-lifecycle.html#onmounted
In Vue 2, instance method this.$forceUpdate() could be used to update the component manually. How can we force update component in Vue 3 - Composition API (inside setup method) ?
setup(props, context) {
const doSomething = () => {
/* how to call $forceUpdate here? */
}
return {
doSomething
}
}
Thanks, in advance.
If using Options API:
<script lang="ts">
import {getCurrentInstance, defineComponent} from 'vue'
export default defineComponent({
setup() {
const instance = getCurrentInstance();
instance?.proxy?.$forceUpdate();
}
})
</script>
If using Composition API with <script setup>
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
instance?.proxy?.$forceUpdate();
</script>
When I need to force an update in vue I usually add a key with a value I can change, which will then force vue to update it. That should work in vue 3 as well, though I admit I haven't ever tried it. Here's an example:
<template>
<ComponentToUpdate
:key="updateKey"
/>
</template>
<script>
export default {
data() {
return {
updateKey: 0,
};
},
methods: {
forceUpdate() {
this.updateKey += 1;
}
}
}
</script>
You can read more about it here: https://michaelnthiessen.com/key-changing-technique/
$forceUpdate is still available in Vue3, but you won't have access to it in the setup() function. If you absolutely need to use it, you can use object API component or this fancy trick...
app.component("my-component", {
template: `...`,
methods: {
forceUpdate(){
this.$forceUpdate()
}
},
setup(props) {
const instance = Vue.getCurrentInstance();
Vue.onBeforeMount(()=>{
// instance.ctx is not populated immediately
instance.ctx.forceUpdate();
})
return {doSomething};
},
})
If this seems like a ridiculous solution, trust your Judgement. Ideally your application would not rely on forceUpdate. If you are relying on it, it likely means that something is miss-configured, and that should be the first thing to resolve.
I'm wondering how I can observe child properties from the parent component in Vue 3 using the composition api (I'm working with the experimental script setup).
<template>//Child.vue
<button
#click="count++"
v-text="'count: ' + count"
/>
</template>
<script setup>
import { ref } from 'vue'
let count = ref(1)
</script>
<template>//Parent.vue
<p>parent: {{ count }}</p> //update me with a watcher
<Child ref="childComponent" />
</template>
<script setup>
import Child from './Child.vue'
import { onMounted, ref, watch } from 'vue'
const childComponent = ref(null)
let count = ref(0)
onMounted(() => {
watch(childComponent.count.value, (newVal, oldVal) => {
console.log(newVal, oldVal);
count.value = newVal
})
})
</script>
I want to understand how I can watch changes in the child component from the parent component. My not working solution is inspired by the Vue.js 2 Solution asked here. So I don't want to emit the count.value but just watch for changes.
Thank you!
The Bindings inside of <script setup> are "closed by default" as you can see here.
However you can explicitly expose certain refs.
For that you use useContext().expose({ ref1,ref2,ref3 })
So simply add this to Child.vue:
import { useContext } from 'vue'
useContext().expose({ count })
and then change the Watcher in Parent.vue to:
watch(() => childComponent.value.count, (newVal, oldVal) => {
console.log(newVal, oldVal);
count.value = newVal
})
And it works!
I've answered the Vue 2 Solution
and it works perfectly fine with Vue 3 if you don't use script setup or explicitly expose properties.
Here is the working code.
Child.vue
<template>
<button #click="count++">Increase</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
return {
count: ref(0),
};
},
};
</script>
Parent.vue
<template>
<div id="app">
<Child ref="childComponent" />
</div>
</template>
<script>
import { ref, onMounted, watch } from 'vue';
import Child from './components/Child.vue';
export default {
components: {
Child,
},
setup() {
const childComponent = ref(null);
onMounted(() => {
watch(
() => childComponent.value.count,
(newVal) => {
console.log({ newVal }) // runs when count changes
}
);
});
return { childComponent };
},
};
</script>
See it live on StackBlitz
Please keep reading
In the Vue 2 Solution I have described that we should use the mounted hook in order to be able to watch child properties.
In Vue 3 however, that's no longer an issue/limitation since the watcher has additional options like flush: 'post' which ensures that the element has been rendered.
Make sure to read the Docs: Watching Template Refs
When using script setup, the public instance of the component it's not exposed and thus, the Vue 2 solutions will not work.
In order to make it work you need to explicitly expose properties:
With script setup
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
With Options API
export default {
expose: ['publicData', 'publicMethod'],
data() {
return {
publicData: 'foo',
privateData: 'bar'
}
},
methods: {
publicMethod() {
/* ... */
},
privateMethod() {
/* ... */
}
}
}
Note: If you define expose in Options API then only those properties will be exposed. The rest will not be accessible from template refs or $parent chains.
I am totally new to Vuejs and my question is:
Is there anyway for v-on to listen on click event, then execute a function which is defined in a module?
For example:
<button v-on:click="executeClick()"></button>
Will execute executeClick() in below module, which will be imported to Vue instance through require:
module.exports = {
executeClick: function () {
// do something
}
}
I am trying to keep vue instance's methods not to be crowded with a bunch of functions.
No, within the model you need features that are in this your component
soluction:
click.js
module.exports = {
executeClick: function () {
}
}
component.vue
<template>
<tag #click="$options.click.executeClick">
</template>
<script>
import click from 'click.js'
export default {
click: click
}
</script>
I'm trying to call a function inside 'method' from outside. However, it isn't working.
Github issue reporting the same: https://github.com/vuejs/vue/issues/329
vm.test(); // call a function in method, not working
this.vue.test() // not working
export default {
methods: {
test: function() {
alert('test fuction called');
}
}
}
It is not very clear what the actual goal of the original poster is, however this is how you can call a method on a Vue instance, after creating it:
var viewModel = new Vue({
el: "#app",
data: {
msg: "Hello there"
},
methods: {
test: function() {
alert('test fuction called');
}
}
});
viewModel.test();
Working example: https://jsfiddle.net/Daryn/Lja7pake/3/
If you are exporting a single file component then try this:
example.js
<script>
export default {
methods: {
test: function() {
alert('test fuction called');
}
}
}
</script>
main.js
<script>
import Thing from './example.js';
Thing.test();
</script>
Reference: https://v2.vuejs.org/v2/guide/single-file-components.html
What you are trying to achieve is fundamentally flawed. You can't call a method of a component unless you have a reference to an instance of that particular component. In your code, which particular component is vm referring to?
All you're doing is exporting a Vue component definition from your module; there's no component being instantiated here.
We'll need to see more of your code or a complete explanation of what exactly you're trying to achieve so we can provide an alternative solution. (Why are you trying to call the component's method outside of its definition?)
export default {
...
methods: {
...
},
mounted () {
EventBus.$on(‘EVENT_NAME’, function (payLoad) {
...
});
}
}
This is the way I solved that problem.
For the purpose of this demonstration, we create a new project using Vue/CLI. After installation finished, we make the vm exposed to global. Open src/main.js and edit like so:
src/main.js
import Vue from 'vue';
import App from './App.vue';
var vm = new Vue({
router,
render: h => h(App)
}).$mount('#app');
// Add this line (tambahkan baris berikut):
window.vm = vm;
Leave the generated App.vue like it is. So the first child of vm (vm.$children[0]) is App.vue.
We see that App.vue have a child. That makes HelloWorld.vue component as a grand children of vm (vm.$children[0].$children[0]). Knowing this, we can call the methods from outside 'export default' like this:
src/components/HelloWorld.vue
<template>
<div class="hello">
<button
id="sebuahButton"
class="btn btn-outline-secondary btn-sm"
type="button"
>Click Me, Jose!</button>
<h1>{{ msg }}</h1>
<!-- and some stuff, vue cli default generated code -->
<div>
</template>
<script>
(function() {
// wait for the DOM ready event in plain JavaScript
document.addEventListener("DOMContentLoaded", event => {
document.getElementById("sebuahButton").onclick = function() {
vm.$children[0].$children[0].someAction();
};
});
})();
export default {
name: "HelloWorld",
props: {
msg: String
}
methods: {
someAction () {
// do something (lakukan sesuatu masbro!)
console.log("It's been called from outer space, Luke!");
}
}
}
</script>