Manually mounted Vue instances and props

When you want to start phasing in Vue into a project, it’s tempting to not rely on custom components. Instead, you’ll likely use manually mounted Vue instances through the vm.$mount method. However, it’s not clear how you can pass in props using this strategy. The props documentation bases all of its examples around custom components. Instead of passing in props, you can rely on the created lifecycle hook. But that doesn’t feel as clean or communicative as using props for their intended purpose. How can you pass in a prop when you’re manually mounting a component?

Manually mounted Vue instances

Vue gives you the ability to create custom HTML components. That’s the way that the documentation suggests you use Vue. It looks like this:

<hello-world></hello-world>

You use a custom tag that Vue then mounts and renders via its DOM-manipulation capabilities. This is great on a greenfield application: it allows you to express your app in a domain-specific way. However, when you use Vue to clean up a mess of jQuery spaghetti, you likely want to keep your existing HTML as a precursor to moving to custom elements.

Manually mounting Vue instances looks like the following:

import Vue from 'vue';
import helloWorld from 'components/hello-world;

const viewModel =
  new Vue(helloWorld).$mount('#pre-existing-id');

This mounts the Vue view model on some pre-existing HTML, such as the following:

<div id="pre-existing-id"></div>

The advantage of this is that you can start to remove your jQuery spaghetti without completely replacing your HTML until you’re ready.

The problem

If you’ve used a recent JavaScript library for rendering components, you’ve probably heard the term “props” bandied about. Props, which I believe is short for “properties”, are values that you pass into your component via attributes on the element. For example:

<card title="HTML Components"
      body="React, Angular, and Vue all allow them">
</card>

The title and body attributes on the <card> element are two “static props” for the card. The card uses them as input data and can use them like any other piece of data. I’m not sure if this part is correct, but it seems like you should use props in non-parent components as data that do not change. Not necessarily immutable, but treated as if they are constants.

A first attempt

My first attempt to use props in this situation looked like this:

<div id="pre-existing-id" title="Hello, world">
  <p>{{ title }}</p>
</div>
const component = {
  props: {
    title: {
      type: String,
      required: true
    }
  }
};

const viewModel =
  new Vue(component).$mount('#pre-existing-id');

This solution raises the following warning and does not render anything:

[Vue warn]: Missing required prop: "title"

This means that, when manually mounted, components don’t care about props that we set on their mounted element. What else could we try?

Getting it to work

The solution to this problem came after I read the Options / Data documentation and saw that you can pass in a propsData attribute. Thus, if you can get the necessary data from somewhere on the page, you can inject it using propsData. To do this, I landed on the following:

<div id="pre-existing-id" data-title="Hello, world">
  <p>{{ title }}</p>
</div>
const componentElement =
  document.getElementById('pre-existing-id');
const title = componentElement.dataset.title;
const component = {
  props: {
    title: {
      type: String,
      required: true
    }
  }
};

const viewModel =
  new Vue({
    ...component,
    propsData: {
      title: title
    }
  }).$mount('#pre-existing-id');

Using this pattern, the data remains where you want it, but within the element’s dataset rather than as a direct prop. You still mount the element manually, but you inject the prop data via the propsData option. The component renders how you want it to and you can move on with your day.

Have you run into this issue? How did you solve it?