I encountered a similar issue in the past, and I managed to overcome it by utilizing deep watch
along with Lodash functions like _.cloneDeep
and _.isEqual
.
Within your child component, establish your own data named componentTask
. Keep a close eye on changes within both componentTask
and your prop
, comparing them using _.isEqual
. Whenever there is a change in componentTask
, trigger an event up to its parent.
SubTask:
<template>
<div>
<input type="text" v-model="componentTask.task">
<input type="number" min="0" v-model.number="componentTask.spentTime">
<SubTask v-if="task.subTasks" @task-change="handleTaskChange" :task="task.subTasks" />
</div>
</template>
<script lang="ts">
import {Vue, Component, Prop, Watch} from 'vue-property-decorator'
import {Task} from "@/components/Test/Test";
import _ from "lodash";
@Component
export default class SubTask extends Vue {
@Prop() task!: Task;
componentTask: Task | undefined = this.task;
@Watch('task', {deep: true, immediate: true})
onTaskChange(val: Task, oldVal: Task) {
if (_.isEqual(this.componentTask, val))
return;
this.componentTask = _.cloneDeep(val);
}
@Watch('componentTask', {deep: true, immediate: true})
onComponentTaskChange(val: Task, oldVal: Task) {
if (_.isEqual(val, this.task))
return;
this.$emit("task-change");
}
handleTaskChange(subTasks: Task){
this.componentTask = subTasks;
}
}
</script>
Parent class:
<template>
<div style="margin-top: 400px">
<h1>Parent Task</h1>
<br>
<div style="display: flex;">
<div style="width: 200px">
<h4>task</h4>
<p>{{task.task}}</p>
<p>{{task.spentTime}}</p>
<br>
</div>
<div style="width: 200px">
<h4>task.subTasks</h4>
<p>{{task.subTasks.task}}</p>
<p>{{task.subTasks.spentTime}}</p>
<br>
</div>
<div style="width: 200px">
<h4>task.subTasks.subTasks</h4>
<p>{{task.subTasks.subTasks.task}}</p>
<p>{{task.subTasks.subTasks.spentTime}}</p>
<br>
</div>
</div>
<SubTask :task="task" @task-change="handleTaskChange"/>
</div>
</template>
<script lang="ts">
import {Vue, Component, Prop} from 'vue-property-decorator'
import SubTask from "@/components/Test/SubTask.vue";
import {defaultTask, Task} from "@/components/Test/Test";
@Component({
components: {SubTask}
})
export default class Test extends Vue {
task: Task = defaultTask;
handleTaskChange(task: Task) {
this.task = task;
}
}
</script>
Defined interface:
export interface Task {
task: string;
spentTime: number;
subTasks?: Task;
}
export const defaultTask: Task = {
task: "Some Task",
spentTime : 2,
subTasks: {
task: "Some Sub Task",
spentTime: 1,
subTasks:{
task:"Some sub sub task",
spentTime:30
}
}
};