digout
digout

Reputation: 4252

How to make a template variable non-reactive in Vue

I have an edit form with variables held in the data(). I don't want the title of the edit page to update yet I want to maintain the v-model sync of data between the input and data. What's the simplest way to make the title non-reactive in the h1 tag? Mr You has to have something up his sleeve for this..

<template>
    <div>
        <h1>{{ title }}</h1>
        <input v-model="title">
    </div>
</template>

<script>    
export default {
    data: {
        title: 'Initial value'      
    }
}
</script>

Upvotes: 2

Views: 4972

Answers (5)

Roy J
Roy J

Reputation: 43881

If you don't want the input to change the value of your data item, use value to bind it rather than the two-way v-model. Then it just acts as an initializer for the input.

If you want to have two values, one that doesn't change and one that does that gets initialized from the other, you need to have two data items. The non-changing one can be a prop with a default value. The other is a data member which, if you use a data function, can initialize itself to the prop value.

new Vue({
  el: '#app',
  props: {
    initTitle: {
      default: 'Initial value'
    }
  },
  data() {
    return {
      title: this.initTitle
    };
  }
});
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <h1>{{ initTitle }}</h1>
  <input v-model="title">
  <div>Title is "{{title}}"</div>
</div>

You could alternatively use the little-known $options properties to define your title as a sort of internal constant rather than a prop. I am of mixed feelings about whether this is a good design approach or a step too weird.

new Vue({
  el: '#app',
  initTitle: 'Initial value',
  data() {
    return {
      title: this.$options.initTitle
    };
  }
});
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <h1>{{ $options.initTitle }}</h1>
  <input v-model="title">
  <div>Title is "{{title}}"</div>
</div>

Upvotes: 3

akuiper
akuiper

Reputation: 214957

You could potentially use v-once directive for your purpose if you don't want to create a separate variable for input. From the docs:

Render the element and component once only. On subsequent re-renders, the element/component and all its children will be treated as static content and skipped.

new Vue({
  el: "#app",
  data: {
    title: "initial value"
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>

<div id="app">
  <input v-model="title">
  <p>Reactive title: {{ title }}</p>
  <p v-once>Static title: {{ title }}</p>
</div>

Upvotes: 5

tony19
tony19

Reputation: 138276

The Vue docs recommend Object.freeze() on the returned object in data() to disable reactivity on properties:

data() {
  return Object.freeze({ title: 'Initial value' })
}

But the caveat is it freezes all properties (it doesn't look like there's a way to freeze only some properties using this method), and using v-model with this causes console errors (Cannot assign to read only property).

Vue.config.devtools = false;
Vue.config.productionTip = false;

new Vue({
  el: '#app',
  data() { 
    return Object.freeze({
      message: 'Hello Vue.js!',
    })
  }
})
<script src="https://unpkg.com/[email protected]"></script>

<div id="app">
  <p>{{ message }}</p>
  <input v-model="message"> <!-- XXX: Cannot use v-model with frozen property. This will cause a console error. -->
</div>

Alternatively, you could arbitrarily remove the reactivity from any configurable data property by redefining it with writeable: false:

methods: {
  removeReactivity() {
    Object.defineProperty(this, 'title', {value: null, writeable: false});
  }
}

Vue.config.devtools = false;
Vue.config.productionTip = false;

new Vue({
  el: '#app',
  data() { 
    return {
      message: 'Hello Vue.js!',
    }
  },
  methods: {
    removeReactivity() {
      Object.defineProperty(this, 'message', {value: null, writeable: false});
    }
  }
})
<script src="https://unpkg.com/[email protected]"></script>

<div id="app">
  <p>{{ message }}</p>
  <input v-model="message">

  <div>
    <button @click="removeReactivity">
      Remove reactivity for <code>message</code>
    </button>
  </div>
</div>

Upvotes: 5

Coffiend
Coffiend

Reputation: 31

There is no easy way to solve your problem with Vue as is since Vue automatically injects reactive getters and setters for all object properties. You could use Object.freeze() on the variable to remove reactivity BUT it would apply across the whole object itself which is not what you want.

I created a fork out of vue called vue-for-babylonians to restrict reactivity and even permit some object properties to be reactive. Check it out here.

With it, you can tell Vue to not make any objects which are stored in vue or vuex from being reactive. You can also tell Vue to make certain subset of object properties reactive. You’ll find performance improves substantially and you enjoy the convenience of storing and passing large objects as you would normally in vue/vuex.

Upvotes: 0

Ian MacDonald
Ian MacDonald

Reputation: 14010

Working backwards from the contents of this blog...

It appears that when you create an object for Vue, it creates the properties with reactive getters and setters. If you then append a property to that object out-of-band, then it won't get the reactive capability, but will still be accessible as a value.

This should solve it for you:

<template>
    <div>
        <h1>{{ titleContainer.value }}</h1>
        <input v-model="title">
    </div>
</template>

<script>    
export default {
    data: {
        titleContainer: {}
    }
}
titleContainer.value = "Initial Value"
</script>

Upvotes: 0

Related Questions