I'm currently upgrading a Laravel Nova project from Nova 3 (which uses Vue2) to Nova 4 (which uses Vue3). And for that reason, we had to do some modification in our custom components.
Like, in Vue3 we have used different method other than $parent
and $children
to fetch the child components of parent compoent. This change worked perfectly.
But, in Vue2 there is $watch
property that used to watch any changes in the child component. And to replicate same functionality in Vue3, I tried several methods, but I am not able to find a solution for it. You can see the difference in below components.
Below are the snippets from both Vue2 and Vue3 components.
Vue2 Component (Nova 3):
<template>
<div class="-mx-8 px-8">
<div v-for="condition in field.conditions" :key="condition.value">
<div v-if="condition.value == value">
<component
v-bind="$props"
:key="subfield"
:field="subfield"
:is="'form-' + subfield.component"
v-for="subfield in condition.fields"
/>
<div class="help-text error-text mt-2 text-danger" v-if="hasError">
{{ firstError }}
</div>
</div>
</div>
</div>
</template>
<script>
import { FormField } from 'laravel-nova'
import { Errors } from 'form-backend-validation'
export default {
mixins: [FormField],
props: {
errors: {
default: () => new Errors(),
},
resource: {
required: true
},
resourceId: {
required: true
},
'base-classes': {
type: Object
}
},
data() {
return {
value: null,
errorClass: 'border-danger',
}
},
mounted() {
this.children.forEach(component => {
if(component.field !== undefined && component.field.attribute === this.field.observed_field_name) {
component.$watch(this.field.customProperty || 'value', (value) => {
this.value = value
}, {deep: true, immediate: true});
}
});
},
computed: {
parent() {
let parent = this.$parent
for (let i = 1; i < (this.field.depth || 1); i++) {
parent = parent.$parent
}
return parent
},
children() {
let children = this.parent.$children
for (let i = 1; i < (this.field.depth || 1); i++) {
children = children.filter(c => c.$children.length).map(c => c.$children[0])
}
return children
},
},
}
</script>
Vue3 Component (Nova 4):
<template>
<div class="-mx-8 px-8">
<div v-for="condition in field.conditions" :key="condition.value">
<div v-if="condition.value == value">
<component
v-bind="$props"
:key="subfield"
:field="subfield"
:is="'form-' + subfield.component"
v-for="subfield in condition.fields"
/>
<div class="help-text error-text mt-2 text-danger" v-if="hasError">
{{ firstError }}
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, watch, onMounted, getCurrentInstance } from 'vue';
import { Errors } from 'form-backend-validation';
export default {
props: {
errors: {
default: () => new Errors(),
},
resource: {
required: true
},
resourceId: {
required: true
},
'base-classes': {
type: Object
},
field: {
type: Object,
required: true
}
},
setup(props) {
const value = ref(null);
const errorClass = 'border-danger';
const instance = getCurrentInstance();
// This needs to be carefully handled, as it relies on internal structure
const children = () => {
return instance.parent.subTree.children;
};
onMounted(() => {
children().forEach(component => {
let child = component.children[0];
if (child.props.field && child.props.field.attribute === props.field.observed_field_name) {
watch(() => child.component, (newValue) => {
value.value = newValue;
}, { deep: true, immediate: true });
}
});
});
// Computed and methods can be added here as needed
return { value, errorClass };
}
}
</script>
To explain more the use of $watch
in project, below is the screenshot for reference.
So here in the screenshot you can see there are fields. And each field is a Vue component itself. They can be Nova default fields or they can be custom one.
The vue compoent code pasted at the top, is Role field (see in second screenshot) and it is a custom component. The Role field is dependent on the Company field. If Company field is blank then Role field will get hide and it will get displayed when Company field is selected (that too for specific values).
So basically $watch
property is used to keep watch on changes in Company field (specific element within Company component - this.field.customProperty). In Vue2 it was easier to handle this as you can see in the Vue2 code. But, in Vue3 I am not finding proper solution or may be I do not know proper code to watch the compoent.
Does any one know how should I watch child component in Vue3?
Please note:
I can not directly access parent or child component to use ref method. Since Laravel Nova does not allow this.
There is new method dependOn in Laravel Nova 4 and by using it I could avoid using this custom component, but I do not have to use it since this custom component is also used for other complex resources(pages) in the project.