Search code examples
typescriptvuejs3

How to track inner changes made to objects through the object's methods in vue3?


I'm currently making a web-app with VueJS3 and Typescript.

I divided my apps with pure TS objects on one side, and Vue components on the other, with the components using the objects to talk with an API, store states, ...

Right now, I'm creating an operation manager for my application, with a main class storing and managing the operations, and the Operations which are objects with states (pending, in_progress, finished, ...), but when I change an Operation state with a method in the Operation, it's not reflected on my page. How can I modify my code to do that

Here's an example of this (you can try it online here):

It's a white page showing the current state of the Operation item, with a timer set to 2 seconds to put it to the next state (from "PENDING" to "IN_PROGRESS"), but after two seconds it does not do anything (when you see "next", it should've changed to "IN_PROGRESS", but it stays on "PENDING").

OperationListItem.vue

<script setup lang="ts">
import Operation from "./Operation";

defineProps<{operation: Operation}>()
</script>

<template>
  <div>
    {{ operation.state }}
  </div>
</template>

Operation.ts

export default class Operation {
  state: "PENDING" | "IN_PROGRESS" | "FINISHED";

  constructor() {
    this.state = "PENDING";
  }

  async next() {
    switch (this.state) {
      case "PENDING": 
        this.state = "IN_PROGRESS";
        break;
      case "IN_PROGRESS":
        this.state = "FINISHED";
        break;
      case "FINISHED":
        break;
    }
  }
}

OperationManager.ts

import Operation from "./Operation";

export default class OperationManager {
  private _operations: Operation[] = [];

  put(operation: Operation) {
    this._operations.push(operation);
  }

  get operations() {
    return this._operations;
  }
}

App.vue

<script setup lang="ts">
import { reactive } from 'vue';
import Operation from './Operation';
import OperationListItem from './OperationListItem.vue';
import OperationManager from './OperationManager';
import { ref } from 'vue';

const opManager = reactive(new OperationManager());

const log = ref("");
const op = new Operation();

opManager.put(op);

const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

sleep(2000).then(_ => {op.next(); log.value += "next" })

</script>

<template>
  <OperationListItem v-for="operation in opManager.operations" :operation="operation" />
  <p>{{ log }}</p>
</template>

Solution

  • When we wrap op in ref it works as expected => playground

    App.vue

    <script setup lang="ts">
    import { reactive, ref } from 'vue';
    import Operation from './Operation';
    import OperationListItem from './OperationListItem.vue';
    import OperationManager from './OperationManager';
    
    const opManager = reactive(new OperationManager());
    
    const log = ref("");
    const op = ref(new Operation());
    
    opManager.put(op.value);
    
    const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
    
    sleep(2000).then(_ => {op.value?.next(); log.value += "next" });
    
    </script>
    
    <template>
      <OperationListItem
        v-for="(operation, index) in opManager.operations"
        :key="operation.state + index"
        :operation="operation" />
      <p>{{ log }}</p>
    </template>
    
    
    

    It is also necessary to set the key property when using v-for.