[Vuejs]-Toggle the value of variable that is being watched in vuejs

3đź‘Ť

âś…

You should use Vue.$nextTick.

if(this.boolVar === true){
  this.boolVar = false;
  this.$nextTick(() => {
    this.boolVar = true;
  })
}

If you don’t want to use $nextTick, you can do like that :

  watch: {
    boolVar: {
      handler(val) {
        console.log(val)
      },
      deep: true
    }
  }

However, why don’t you just make a doSomeWork() function, call it in your watcher, and when you need to re-execute that logic, you call the function rather than toggling your boolean? That would make more sense to me, but maybe I’m missing some details. :

watch: {
   boolVar: function (val) {
     if(val === true){
       this.doingSomeTasks()
     }
   }
}

And :

if(this.boolVar === true){
  this.doingSomeTasks()
}
👤Julien

1đź‘Ť

Your code does not work as you expect because you expect that all watch handlers are executed synchronously but they are not. Vue is using Async Update Queue

Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once. This buffered de-duplication is important in avoiding unnecessary calculations and DOM manipulations. Then, in the next event loop “tick”, Vue flushes the queue and performs the actual (already de-duped) work.

This means that when you execute this.boolVar = false;this.boolVar = true; – both changes are just "registered" by Vue. When all your code finished executing, "next event loop" starts – Vue sees the change in the queue, finds the registered watcher and checks the old (= value when the handler was last executed) and new value. Which are in your case both true …so from the Vue point of view there is no change and no need to execute the handler….

From technical POV the solution is to postpone 2nd change (this.boolVar = true;) to some later "tick" so Vue "sees" the false 1st and in the next tick it will run the watcher (false -> true)

But good code is not just the code that works but also the code that is communicating programmer’s intent and is easy to understand. this.boolVar = false;this.boolVar = true; is violating this principle in a big way

You need to run someTasks when your variable changes from false to true. That’s fine. But you also want to run someTasks in other scenario when the variable is already true. It is clear that someTasks should not be in the watcher but extracted to independent method as @Julien already suggested…

Update …just to be complete

What you want is possible in Vue 3 with the new flush option of the watch – just use value sync and your watcher will be executed synchronously anytime the value changes. See the demo below…

const app = Vue.createApp({
  data() {
    return {
      value: true,
      flushMode: 'sync',
      unwatch: null
    }
  },
  methods: {
    toggle() {
      this.value = false
      this.value = true
    }
  },
  watch: {
    flushMode: {
      handler: function() {
        this.unwatch && this.unwatch()
        this.unwatch = this.$watch('value', 
          function(newValue) {
            console.log(`New value = ${newValue}`)
          }, 
          { flush: this.flushMode }
        )
      },
      immediate: true    
    }
  },
  updated() {
    console.log('Component updated')
  }
})

app.mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.1.1/vue.global.prod.min.js" integrity="sha512-vRIbP8hGYYKnayuLhVRwQXTxvAJ/wUm4vqIEzIBKLfDcM2ArSENuojG1jEIactQ7F/0unOe9VQ+U9KagTEyQvA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div id="app">
  <div>
    Variable: {{ value }}
  </div>
  <button @click="toggle">Toggle</button>
  <div>
    <label for="pre">Flush mode:</lael>
    <input type="radio" id="pre" value="pre" v-model="flushMode" /> pre (default)
    <input type="radio" id="sync" value="sync" v-model="flushMode" /> sync
  </div>
</div>

Leave a comment