Skip to content
Advertisement

global update event for Vue.js

In Vue.js, is there a way to register an event if any component updates its data?

My usecase: I am modeling a RPG character via a set of Javascript classes. The TCharacter class has several attributes that can be modified: name, level, HP, magic. While “name” is a simple string, “HP” and “magic” is a custom class TResource which has its own consumption and refill rules.

Instance of the TCharacter class is a source of truth, and I created some Vue components that are views of it.

I created a character component and a resource component in Vue, vaguely like this:

<div class=template id=character>
   <input v-model="ch.name">
   <resource :attr="ch.magic"></resource>
   <resource :attr="ch.hp"></resource>
</div>

<div class="template" id="resource">
   you have {{ attr.available }} points
   <button @click="attr.consume">X</button>
</div>

<div id="main">
   <character :ch="lancelot"></character>
</div>

and the javascript:

class TCharacter {
  constructor() {
    this.name = "Lancelot"
    this.hp = new Resource(20)
    this.magic = new Resource(10)
  }
}

class TResource {
  constructor(limit) {
    this.available = limit
    this.limit = limit
  }

  consume() {
    if (this.available > 0) this.available--;
  }
}

let lancelot = new TCharacter()

Vue.component('character', {
  template: '#character',
  props: ['ch'],
})

Vue.component('resource', {
  template: '#resource',
  props: ['attr'],
})

new Vue({
  el: "#main",
  data() { return { lancelot } }
})

(I’m not sure the code works exactly as written, but hopefully the intent is clear. Something very similar to this is already working for me.)

Now, I’d like to save the character object to localstorage every time the user makes a modification: changes its name, clicks on a button that consumes a point of magic, etc.

So for instance, I want to be notified that the value of ch.name changed because the user typed something into the input box. Or that a magic point was lost because the user clicked a button for that.

I could detect changes to the character component by installing an updated() handler, which notifies me whenever a DOM is modified (viz). However, this won’t trigger when the child component resource is modified. I’d need to add a separate updated() handler to all other components. This gets tedious very fast.

I’m imagining something like a global updated() handler that would fire any time any component has registered a change. Or better, a way to specify that update should fire on component’s children changes as well.

edit: I have reworded parts of the question to clarify what I’m trying to accomplish. Some of you already suggested Vuex. But, from what I understood, Vuex enforces being the single source of truth — I already have a single source of truth. How is Vuex different / better?

Advertisement

Answer

You’re going to need a serialized version of lancelot to write out. You can do that with a computed. Then you can watch the computed to see when anything changes.

Alternatively, you could watch each individual trait, and write it out as it changes.

class TCharacter {
  constructor() {
    this.name = "Lancelot"
    this.hp = new TResource(20)
    this.magic = new TResource(10)
  }
}

class TResource {
  constructor(limit) {
    this.available = limit
    this.limit = limit
  }

  consume() {
    if (this.available > 0) this.available--;
  }
}

let lancelot = new TCharacter()

Vue.component('character', {
  template: '#character',
  props: ['ch'],
})

Vue.component('resource', {
  template: '#resource',
  props: ['attr'],
})

const vm = new Vue({
  el: "#main",
  data() {
    return {
      lancelot
    }
  },
  computed: {
    serializedLancelot() {
      return JSON.stringify(this.lancelot);
    }
  },
  watch: {
    serializedLancelot(newValue) {
      console.log("Save update:", newValue);
    }
  }
});

setTimeout(() => {
  vm.lancelot.hp.consume();
}, 500);
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="main">
</div>
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement