matejcik
matejcik

Reputation: 2072

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?

Upvotes: 0

Views: 918

Answers (2)

Roy J
Roy J

Reputation: 43881

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>

Upvotes: 1

Jaya
Jaya

Reputation: 3911

Am not sure I understand the use case in entirety, but if my assumption is right, you need to update components based on an object's update (updates to properties of an object), for that you could use Vuex . Although am not sure if you are restricted to use an additional library

Here as an example, you could add a state value named character which is an object, something along the lines of

const state = {
 character = {}; 
}

And now you can mutate this using vuex mutations.

commit('set_character', your_new_value)

Now since you said you need to update all or some components based on any mutation to character, use vuex plugins to listen to any mutation to that object, and update the state of the components.

 store.subscribe(mutation => {
  if (mutation.type === 'set_character') {
    // do whatever you want here
  }
})

All of the above is just an outline based on what you mentioned, but this is just a starter, you may or may not want to add character into the store's state but simply the properties such as magic or hp.

Upvotes: 0

Related Questions