I have this general component which can render different sorts of components asynchroneously:
<script setup>
import { defineAsyncComponent, defineProps, defineEmits, defineExpose, onMounted, ref, computed, toRefs, nextTick, watch } from 'vue';
const props = defineProps({
component: { type: String },
});
const { component } = toRefs(props);
const components = {
componentOne: defineAsyncComponent(() => import('...'),
componentTwo: defineAsyncComponent(() => import('...'),
// and so on
}
const currentComponent = computed(() => {
return component[props.component] || null
})
const currentComponentRef = ref(null);
const methodA = ref(() => null)
onMounted(async () => {
if (currentComponentRef.value && currentComponentRef.value.methodA) {
methodA.value = currentComponentRef.value.methodA
}
})
watch(currentComponentRef, async (newVal) => {
if (newVal) {
await nextTick()
if (newVal) {
methodA.value = newVal.methodA
}
}
})
defineExpose({
methodA
})
</script>
<template>
<component
:is="currentComponent"
ref="currentComponentRef"
/>
</template>
Now when using this component from a parent component, calling its methodA onMounted gets on the call stack before the component can define methodA itself. I want to avoid using timeouts in the parent.
Is there another way to make sure exposed methods are ready to be called onMounted ?
The only way I found on the parent component to wait is the following:
const methodAFromComponent = computed(() => {
if(component.value && component.value.methodA) {
return component.value.methodA
}
})
watch(methodAFromComponent, (newVal) => {
if (newVal) {
console.log(newVal())
}
})
But if there is a way to avoid users of the component to have to do that, it would be ideal.
You can postpone calls in a reactive array and when your async component is ready (watch it) - execute the calls:
<script setup>
import { defineExpose, ref, watch, defineAsyncComponent, shallowReactive} from 'vue';
const props = defineProps({component: String});
const components = {
componentOne: defineAsyncComponent(async () => ({setup: (_, {expose}) => (expose({methodA: () => alert('componentOne')}), () => 'componentOne')})),
componentTwo: defineAsyncComponent(async () => ({setup: (_, {expose}) => (expose({methodA: () => alert('componentTwo')}), () => 'componentTwo')}))
};
const currentComponentRef = ref(null);
const methodACalls = shallowReactive([]);
watch([methodACalls, currentComponentRef], () => {
if(!currentComponentRef.value) return;
while(methodACalls.length) currentComponentRef.value.methodA(...methodACalls.shift());
})
defineExpose({
methodA: (...args) => methodACalls.push(args)
});
</script>
<template>
<component
:is="components[component]"
ref="currentComponentRef"
/>
</template>
Usage:
<script setup>
import {ref, onMounted} from 'vue';
import Parent from './Parent.vue';
const component = ref('componentOne');
const $parent = ref();
onMounted(() => {
$parent.value.methodA();
});
</script>
<template>
<parent ref="$parent" :component="component"/>
</template>