4

I'd like to use Vue with pre-rendered HTML. In the long run we might not, but for now it seems easier to leave the server code in place and use Vue to manage the client logic of the web app.

I can't seem to find anything that describes how to have the view model load its data from the HTML. My example will be the same as https://github.com/vuejs/Discussion/issues/56. Even though that issue was closed (and fixed?), I can't seem to get that to work in either 1.x or 2.x.

When I set up some HTML

<div id="myvue">
    <span v-text="message">Hello!</span>
</div>

and then create a view-model for it

var vm = new Vue({
    el: '#myvue'
});

I'm hoping to have the vm object hydrated with data from the HTML so that vm.message == 'Hello!' is true. But that doesn't happen. I just end up with an error saying that message is not defined with version 1. With version 2 I get a warning that says Property or method "message" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.

But when I declare the data property in the vm like so

var myvue = new Vue({
    el: '#myvue',
    data: {
        message: <something>
    }
});

no matter what <something> is (null, a string, undefined), it always replaces what's in the HTML element. Is there any way to have the Vue object load data from the HTML?

3 Answers 3

0

It is theoretically impossible to hydrate your state directly from HTML, because rendering is not a bijective function. Two simple examples:

  • <p>{{ foo }}</p> renders to <p>2</p>. Is foo a string or a number?
  • <p>{{ foo + bar }}</p> renders to <p>6</p>. What are the values of foo and bar?

The simplest way to hydrate your state is to take the data you use for rendering on the server, serialize it to JSON and render it using <script>window.hydrationData = ...</script> directly in your document. This way, you can use it for data when you create the Vue instance.

This approach is also used in the official Vue demo app.

Sign up to request clarification or add additional context in comments.

1 Comment

Hi mzgajner, thanks for the answer. I hadn't considered it from that perspective, but I can imagine that there are some basic things that I could pull from the html when they're not dependent on calculations. Like loading all of a span's text into a model. I'm posting an answer referring to knockout.js with more detail.
0

It's not related to Vue, but Knockout.js can kind of do this when used with ErikSchierboom's knockout-pre-rendered custom binding.

This combo essentially lets you define an input or something

<div id="mydiv">
    <input data-bind="init, textInput: content" value="foo"/>
</div>

And then bind a model object to it

function ViewModel() {
    this.content = ko.observable();
}

var vm = new ViewModel();
ko.applyBindings(vm, $('#mydiv')[0])

With the init binding on the input, the vm content gets loaded with the value from the input when it gets bound.

Again, it's not Vue. But thought I'd put it up incase it helps someone.

Comments

0

One option, if your rendered HTML doesn't depend on computed properties, might be to parse your generated HTML to grab the values you need for your data (say using jQuery or vanilla-js to create a data object to pass to your VM when you instantiate it:

$(function(){
  var app = '#app';
  var $app = $(app);
  var data = {
    a: 0, // some initial value not present in HTML
    b: $app.find('.foo').text(),
    c: $app.find('input[name=c]').val()
  };
  var vm = new Vue({
    el: app,
    template: '...template...', // if needed
    data: data,
    ...
  });
});

When instantiated, the data model should match the rendered HTML. You may need to supply the template if not all template components are part for the rendered HTML. Note that this most likely will cause the app to re-render once instantiated. You might be able to add the server-rendered="true" attribute to your root app element to prevent this if the pre-rendered HTML is identical to what the vm would render.

Another, not so automated option would be to have your template generate a <script> tag just below your element which the data model in it:

<script>
  window.MYSTATE = window.MYSTATE || {};
  window.MYSTATE['#app'] = {
    "a": 0,
    "b": "Some text",
    "c": "Some value"
  };
</script>

Make sure your data is properly escaped into JSON format. Then in your page scripts where you instantiate your app , use the window.MYSTATE['#app'] as the data object (doing a deep clone to prevent changes in the window.MYSTATE instance affecting your model.

var vm = new Vue({
  el: '#app',
  template: '...template if needed...',
  data: $.extend(true, {}, window.MYSTATE['#app'])
  ...
});

Just make sure you do the instantiation after the page has loaded (i.e. by placing this at the bottom of the page before the closing body tag)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.