I'm using VueJS 3.4.25 and options API for this.
So I have these two components defined like this:
// ComponentA
<template>
...
<div ref="componentA">
<slot></slot>
</div>
...
</template>
<script>
export default {
...
methods: {
fireComponentAMethod() {
const slotNode = this.$slots.default()[0];
slotNode.type.methods.fireComponentBMethod();
},
},
}
</script>
// ComponentB
<template>
...
isValueSet: {{ isValueSet }}
...
</template>
<script>
export default {
...
data() {
return {
isValueSet: false,
};
},
methods: {
fireComponentBMethod() {
console.log("I just fired fireComponentBMethod");
this.isValueSet = true;
console.log(this.isValueSet);
},
},
}
</script>
Ans somewhere else I set up the components in this manner:
// Some absolutely unrelated component
<ComponentA>
<ComponentB />
</ComponentA>
The reasoning for this I'm aiming at greater reusability of the code, hence sometimes it's ComponentB
, sometimes ComponentC
and ComponentDoubleD
inside ComponentA
.
The problem is observed when fireComponentAMethod
is fired. It indeed fires fireComponentBMethod
and in the console I do see
I just fired fireComponentBMethod
true
But the change is not reflected in the ComponentB
template, on the page it constantly stays isValueSet: false
.
I tried using $refs
to access the children, but I guess since they're in the slot, it doesn't work. For example, console.log(this.$refs)
prints out this:
Proxy(Object) {componentA: div}
[[Target]]: Object
componentA: div
0: div
When calling this.$refs.componentA[0]
it doesn't return a component instance. Instead, it returns an object representing a simple node... and I can't call any methods or change data on it.
I have been researching this some more, and thanks to @cantdocpp answer, cooked up the solution with provide/inject approached offered by Vue. Although to me this more looks like a workaround and a hack, it's the closest that I need.
// ComponentA
<template>
...
<div ref="componentA">
<slot></slot>
<button @click="fireComponentAMethod" />
</div>
...
</template>
<script>
import { computed } from "vue";
export default {
...
data() {
return {
triggered: null,
};
},
provide() {
return {
triggered: computed(() => this.triggered),
};
},
methods: {
fireComponentAMethod() {
this.triggered = new Date();
},
},
}
</script>
// ComponentB
<template>
...
isValueSet: {{ isValueSet }}
...
</template>
<script>
export default {
...
data() {
return {
isValueSet: false,
};
},
methods: {
fireComponentBMethod() {
console.log("I just fired fireComponentBMethod");
this.isValueSet = true;
console.log(this.isValueSet);
},
},
inject: ["triggered"],
watch: {
'triggered': function (n, o) {
this.fireComponentBMethod();
},
},
}
</script>
Now this approach will change triggered
on the button click and will trigger the update in ComponentB
. I use new Date()
to make sure the triggered
value is always unique. This changes the values in data
, which was the main goal.